cleanup: removed "#if 0" from source
[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 /* Some compiler can't cast u64 to double
303  * This function do the job for us:
304
305  * We use the highest bit for cast, this only
306  * works if the highest bit is not
307  * in use (This should not happen)
308  *
309  * We used this for all compiler
310  */
311 double
312 u64ToDouble(u64 value)
313 {
314   double r;
315   u64 tmp = value & u64Const(0x7fffffffffffffff);
316   r = (double)(s64)tmp;
317   if (value & u64Const(0x8000000000000000))
318        r +=  9.2233720368547758080e18; /* 2^63 */
319  return r;
320 }
321
322 /* Fake up flags for now, as we aren't keeping track of castling
323    availability yet. [HGM] Change of logic: the flag now only
324    indicates the type of castlings allowed by the rule of the game.
325    The actual rights themselves are maintained in the array
326    castlingRights, as part of the game history, and are not probed
327    by this function.
328  */
329 int
330 PosFlags(index)
331 {
332   int flags = F_ALL_CASTLE_OK;
333   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
334   switch (gameInfo.variant) {
335   case VariantSuicide:
336     flags &= ~F_ALL_CASTLE_OK;
337   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
338     flags |= F_IGNORE_CHECK;
339   case VariantLosers:
340     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
341     break;
342   case VariantAtomic:
343     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
344     break;
345   case VariantKriegspiel:
346     flags |= F_KRIEGSPIEL_CAPTURE;
347     break;
348   case VariantCapaRandom: 
349   case VariantFischeRandom:
350     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
351   case VariantNoCastle:
352   case VariantShatranj:
353   case VariantCourier:
354     flags &= ~F_ALL_CASTLE_OK;
355     break;
356   default:
357     break;
358   }
359   return flags;
360 }
361
362 FILE *gameFileFP, *debugFP;
363
364 /* 
365     [AS] Note: sometimes, the sscanf() function is used to parse the input
366     into a fixed-size buffer. Because of this, we must be prepared to
367     receive strings as long as the size of the input buffer, which is currently
368     set to 4K for Windows and 8K for the rest.
369     So, we must either allocate sufficiently large buffers here, or
370     reduce the size of the input buffer in the input reading part.
371 */
372
373 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
374 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
375 char thinkOutput1[MSG_SIZ*10];
376
377 ChessProgramState first, second;
378
379 /* premove variables */
380 int premoveToX = 0;
381 int premoveToY = 0;
382 int premoveFromX = 0;
383 int premoveFromY = 0;
384 int premovePromoChar = 0;
385 int gotPremove = 0;
386 Boolean alarmSounded;
387 /* end premove variables */
388
389 char *ics_prefix = "$";
390 int ics_type = ICS_GENERIC;
391
392 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
393 int pauseExamForwardMostMove = 0;
394 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
395 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
396 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
397 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
398 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
399 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
400 int whiteFlag = FALSE, blackFlag = FALSE;
401 int userOfferedDraw = FALSE;
402 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
403 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
404 int cmailMoveType[CMAIL_MAX_GAMES];
405 long ics_clock_paused = 0;
406 ProcRef icsPR = NoProc, cmailPR = NoProc;
407 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
408 GameMode gameMode = BeginningOfGame;
409 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
410 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
411 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
412 int hiddenThinkOutputState = 0; /* [AS] */
413 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
414 int adjudicateLossPlies = 6;
415 char white_holding[64], black_holding[64];
416 TimeMark lastNodeCountTime;
417 long lastNodeCount=0;
418 int have_sent_ICS_logon = 0;
419 int movesPerSession;
420 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
421 long timeControl_2; /* [AS] Allow separate time controls */
422 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
423 long timeRemaining[2][MAX_MOVES];
424 int matchGame = 0;
425 TimeMark programStartTime;
426 char ics_handle[MSG_SIZ];
427 int have_set_title = 0;
428
429 /* animateTraining preserves the state of appData.animate
430  * when Training mode is activated. This allows the
431  * response to be animated when appData.animate == TRUE and
432  * appData.animateDragging == TRUE.
433  */
434 Boolean animateTraining;
435
436 GameInfo gameInfo;
437
438 AppData appData;
439
440 Board boards[MAX_MOVES];
441 /* [HGM] Following 7 needed for accurate legality tests: */
442 signed char  epStatus[MAX_MOVES];
443 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
444 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
445 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 char  FENepStatus;
449 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
450 int loadFlag = 0; 
451 int shuffleOpenings;
452 int mute; // mute all sounds
453
454 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
455     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
456         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
457     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
458         BlackKing, BlackBishop, BlackKnight, BlackRook }
459 };
460
461 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
462     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
463         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
464     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
465         BlackKing, BlackKing, BlackKnight, BlackRook }
466 };
467
468 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
469     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
470         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
471     { BlackRook, BlackMan, BlackBishop, BlackQueen,
472         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
473 };
474
475 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
476     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
477         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
478     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
479         BlackKing, BlackBishop, BlackKnight, BlackRook }
480 };
481
482 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
483     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
484         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
485     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
486         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
487 };
488
489
490 #if (BOARD_SIZE>=10)
491 ChessSquare ShogiArray[2][BOARD_SIZE] = {
492     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
493         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
494     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
495         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
496 };
497
498 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
500         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
502         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
506     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
507         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
509         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
510 };
511
512 ChessSquare GreatArray[2][BOARD_SIZE] = {
513     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
514         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
515     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
516         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
517 };
518
519 ChessSquare JanusArray[2][BOARD_SIZE] = {
520     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
521         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
522     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
523         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
524 };
525
526 #ifdef GOTHIC
527 ChessSquare GothicArray[2][BOARD_SIZE] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
529         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
531         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
532 };
533 #else // !GOTHIC
534 #define GothicArray CapablancaArray
535 #endif // !GOTHIC
536
537 #ifdef FALCON
538 ChessSquare FalconArray[2][BOARD_SIZE] = {
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
540         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
542         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
543 };
544 #else // !FALCON
545 #define FalconArray CapablancaArray
546 #endif // !FALCON
547
548 #else // !(BOARD_SIZE>=10)
549 #define XiangqiPosition FIDEArray
550 #define CapablancaArray FIDEArray
551 #define GothicArray FIDEArray
552 #define GreatArray FIDEArray
553 #endif // !(BOARD_SIZE>=10)
554
555 #if (BOARD_SIZE>=12)
556 ChessSquare CourierArray[2][BOARD_SIZE] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
560         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
561 };
562 #else // !(BOARD_SIZE>=12)
563 #define CourierArray CapablancaArray
564 #endif // !(BOARD_SIZE>=12)
565
566
567 Board initialPosition;
568
569
570 /* Convert str to a rating. Checks for special cases of "----",
571
572    "++++", etc. Also strips ()'s */
573 int
574 string_to_rating(str)
575   char *str;
576 {
577   while(*str && !isdigit(*str)) ++str;
578   if (!*str)
579     return 0;   /* One of the special "no rating" cases */
580   else
581     return atoi(str);
582 }
583
584 void
585 ClearProgramStats()
586 {
587     /* Init programStats */
588     programStats.movelist[0] = 0;
589     programStats.depth = 0;
590     programStats.nr_moves = 0;
591     programStats.moves_left = 0;
592     programStats.nodes = 0;
593     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
594     programStats.score = 0;
595     programStats.got_only_move = 0;
596     programStats.got_fail = 0;
597     programStats.line_is_book = 0;
598 }
599
600 void
601 InitBackEnd1()
602 {
603     int matched, min, sec;
604
605     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
606
607     GetTimeMark(&programStartTime);
608     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
609
610     ClearProgramStats();
611     programStats.ok_to_send = 1;
612     programStats.seen_stat = 0;
613
614     /*
615      * Initialize game list
616      */
617     ListNew(&gameList);
618
619
620     /*
621      * Internet chess server status
622      */
623     if (appData.icsActive) {
624         appData.matchMode = FALSE;
625         appData.matchGames = 0;
626 #if ZIPPY       
627         appData.noChessProgram = !appData.zippyPlay;
628 #else
629         appData.zippyPlay = FALSE;
630         appData.zippyTalk = FALSE;
631         appData.noChessProgram = TRUE;
632 #endif
633         if (*appData.icsHelper != NULLCHAR) {
634             appData.useTelnet = TRUE;
635             appData.telnetProgram = appData.icsHelper;
636         }
637     } else {
638         appData.zippyTalk = appData.zippyPlay = FALSE;
639     }
640
641     /* [AS] Initialize pv info list [HGM] and game state */
642     {
643         int i, j;
644
645         for( i=0; i<MAX_MOVES; i++ ) {
646             pvInfoList[i].depth = -1;
647             epStatus[i]=EP_NONE;
648             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
649         }
650     }
651
652     /*
653      * Parse timeControl resource
654      */
655     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
656                           appData.movesPerSession)) {
657         char buf[MSG_SIZ];
658         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
659         DisplayFatalError(buf, 0, 2);
660     }
661
662     /*
663      * Parse searchTime resource
664      */
665     if (*appData.searchTime != NULLCHAR) {
666         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
667         if (matched == 1) {
668             searchTime = min * 60;
669         } else if (matched == 2) {
670             searchTime = min * 60 + sec;
671         } else {
672             char buf[MSG_SIZ];
673             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
674             DisplayFatalError(buf, 0, 2);
675         }
676     }
677
678     /* [AS] Adjudication threshold */
679     adjudicateLossThreshold = appData.adjudicateLossThreshold;
680     
681     first.which = "first";
682     second.which = "second";
683     first.maybeThinking = second.maybeThinking = FALSE;
684     first.pr = second.pr = NoProc;
685     first.isr = second.isr = NULL;
686     first.sendTime = second.sendTime = 2;
687     first.sendDrawOffers = 1;
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695     first.program = appData.firstChessProgram;
696     second.program = appData.secondChessProgram;
697     first.host = appData.firstHost;
698     second.host = appData.secondHost;
699     first.dir = appData.firstDirectory;
700     second.dir = appData.secondDirectory;
701     first.other = &second;
702     second.other = &first;
703     first.initString = appData.initString;
704     second.initString = appData.secondInitString;
705     first.computerString = appData.firstComputerString;
706     second.computerString = appData.secondComputerString;
707     first.useSigint = second.useSigint = TRUE;
708     first.useSigterm = second.useSigterm = TRUE;
709     first.reuse = appData.reuseFirst;
710     second.reuse = appData.reuseSecond;
711     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
712     second.nps = appData.secondNPS;
713     first.useSetboard = second.useSetboard = FALSE;
714     first.useSAN = second.useSAN = FALSE;
715     first.usePing = second.usePing = FALSE;
716     first.lastPing = second.lastPing = 0;
717     first.lastPong = second.lastPong = 0;
718     first.usePlayother = second.usePlayother = FALSE;
719     first.useColors = second.useColors = TRUE;
720     first.useUsermove = second.useUsermove = FALSE;
721     first.sendICS = second.sendICS = FALSE;
722     first.sendName = second.sendName = appData.icsActive;
723     first.sdKludge = second.sdKludge = FALSE;
724     first.stKludge = second.stKludge = FALSE;
725     TidyProgramName(first.program, first.host, first.tidy);
726     TidyProgramName(second.program, second.host, second.tidy);
727     first.matchWins = second.matchWins = 0;
728     strcpy(first.variants, appData.variant);
729     strcpy(second.variants, appData.variant);
730     first.analysisSupport = second.analysisSupport = 2; /* detect */
731     first.analyzing = second.analyzing = FALSE;
732     first.initDone = second.initDone = FALSE;
733
734     /* New features added by Tord: */
735     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
736     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
737     /* End of new features added by Tord. */
738     first.fenOverride  = appData.fenOverride1;
739     second.fenOverride = appData.fenOverride2;
740
741     /* [HGM] time odds: set factor for each machine */
742     first.timeOdds  = appData.firstTimeOdds;
743     second.timeOdds = appData.secondTimeOdds;
744     { int norm = 1;
745         if(appData.timeOddsMode) {
746             norm = first.timeOdds;
747             if(norm > second.timeOdds) norm = second.timeOdds;
748         }
749         first.timeOdds /= norm;
750         second.timeOdds /= norm;
751     }
752
753     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
754     first.accumulateTC = appData.firstAccumulateTC;
755     second.accumulateTC = appData.secondAccumulateTC;
756     first.maxNrOfSessions = second.maxNrOfSessions = 1;
757
758     /* [HGM] debug */
759     first.debug = second.debug = FALSE;
760     first.supportsNPS = second.supportsNPS = UNKNOWN;
761
762     /* [HGM] options */
763     first.optionSettings  = appData.firstOptions;
764     second.optionSettings = appData.secondOptions;
765
766     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
767     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
768     first.isUCI = appData.firstIsUCI; /* [AS] */
769     second.isUCI = appData.secondIsUCI; /* [AS] */
770     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
771     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
772
773     if (appData.firstProtocolVersion > PROTOVER ||
774         appData.firstProtocolVersion < 1) {
775       char buf[MSG_SIZ];
776       sprintf(buf, _("protocol version %d not supported"),
777               appData.firstProtocolVersion);
778       DisplayFatalError(buf, 0, 2);
779     } else {
780       first.protocolVersion = appData.firstProtocolVersion;
781     }
782
783     if (appData.secondProtocolVersion > PROTOVER ||
784         appData.secondProtocolVersion < 1) {
785       char buf[MSG_SIZ];
786       sprintf(buf, _("protocol version %d not supported"),
787               appData.secondProtocolVersion);
788       DisplayFatalError(buf, 0, 2);
789     } else {
790       second.protocolVersion = appData.secondProtocolVersion;
791     }
792
793     if (appData.icsActive) {
794         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
795     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
796         appData.clockMode = FALSE;
797         first.sendTime = second.sendTime = 0;
798     }
799     
800 #if ZIPPY
801     /* Override some settings from environment variables, for backward
802        compatibility.  Unfortunately it's not feasible to have the env
803        vars just set defaults, at least in xboard.  Ugh.
804     */
805     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
806       ZippyInit();
807     }
808 #endif
809     
810     if (appData.noChessProgram) {
811         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812         sprintf(programVersion, "%s", PACKAGE_STRING);
813     } else {
814       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
817     }
818
819     if (!appData.icsActive) {
820       char buf[MSG_SIZ];
821       /* Check for variants that are supported only in ICS mode,
822          or not at all.  Some that are accepted here nevertheless
823          have bugs; see comments below.
824       */
825       VariantClass variant = StringToVariant(appData.variant);
826       switch (variant) {
827       case VariantBughouse:     /* need four players and two boards */
828       case VariantKriegspiel:   /* need to hide pieces and move details */
829       /* case VariantFischeRandom: (Fabien: moved below) */
830         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831         DisplayFatalError(buf, 0, 2);
832         return;
833
834       case VariantUnknown:
835       case VariantLoadable:
836       case Variant29:
837       case Variant30:
838       case Variant31:
839       case Variant32:
840       case Variant33:
841       case Variant34:
842       case Variant35:
843       case Variant36:
844       default:
845         sprintf(buf, _("Unknown variant name %s"), appData.variant);
846         DisplayFatalError(buf, 0, 2);
847         return;
848
849       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
850       case VariantFairy:      /* [HGM] TestLegality definitely off! */
851       case VariantGothic:     /* [HGM] should work */
852       case VariantCapablanca: /* [HGM] should work */
853       case VariantCourier:    /* [HGM] initial forced moves not implemented */
854       case VariantShogi:      /* [HGM] drops not tested for legality */
855       case VariantKnightmate: /* [HGM] should work */
856       case VariantCylinder:   /* [HGM] untested */
857       case VariantFalcon:     /* [HGM] untested */
858       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859                                  offboard interposition not understood */
860       case VariantNormal:     /* definitely works! */
861       case VariantWildCastle: /* pieces not automatically shuffled */
862       case VariantNoCastle:   /* pieces not automatically shuffled */
863       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864       case VariantLosers:     /* should work except for win condition,
865                                  and doesn't know captures are mandatory */
866       case VariantSuicide:    /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantGiveaway:   /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantTwoKings:   /* should work */
871       case VariantAtomic:     /* should work except for win condition */
872       case Variant3Check:     /* should work except for win condition */
873       case VariantShatranj:   /* should work except for all win conditions */
874       case VariantBerolina:   /* might work if TestLegality is off */
875       case VariantCapaRandom: /* should work */
876       case VariantJanus:      /* should work */
877       case VariantSuper:      /* experimental */
878       case VariantGreat:      /* experimental, requires legality testing to be off */
879         break;
880       }
881     }
882
883     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
884     InitEngineUCI( installDir, &second );
885 }
886
887 int NextIntegerFromString( char ** str, long * value )
888 {
889     int result = -1;
890     char * s = *str;
891
892     while( *s == ' ' || *s == '\t' ) {
893         s++;
894     }
895
896     *value = 0;
897
898     if( *s >= '0' && *s <= '9' ) {
899         while( *s >= '0' && *s <= '9' ) {
900             *value = *value * 10 + (*s - '0');
901             s++;
902         }
903
904         result = 0;
905     }
906
907     *str = s;
908
909     return result;
910 }
911
912 int NextTimeControlFromString( char ** str, long * value )
913 {
914     long temp;
915     int result = NextIntegerFromString( str, &temp );
916
917     if( result == 0 ) {
918         *value = temp * 60; /* Minutes */
919         if( **str == ':' ) {
920             (*str)++;
921             result = NextIntegerFromString( str, &temp );
922             *value += temp; /* Seconds */
923         }
924     }
925
926     return result;
927 }
928
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
931     int result = -1; long temp, temp2;
932
933     if(**str != '+') return -1; // old params remain in force!
934     (*str)++;
935     if( NextTimeControlFromString( str, &temp ) ) return -1;
936
937     if(**str != '/') {
938         /* time only: incremental or sudden-death time control */
939         if(**str == '+') { /* increment follows; read it */
940             (*str)++;
941             if(result = NextIntegerFromString( str, &temp2)) return -1;
942             *inc = temp2 * 1000;
943         } else *inc = 0;
944         *moves = 0; *tc = temp * 1000; 
945         return 0;
946     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
947
948     (*str)++; /* classical time control */
949     result = NextTimeControlFromString( str, &temp2);
950     if(result == 0) {
951         *moves = temp/60;
952         *tc    = temp2 * 1000;
953         *inc   = 0;
954     }
955     return result;
956 }
957
958 int GetTimeQuota(int movenr)
959 {   /* [HGM] get time to add from the multi-session time-control string */
960     int moves=1; /* kludge to force reading of first session */
961     long time, increment;
962     char *s = fullTimeControlString;
963
964     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
965     do {
966         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968         if(movenr == -1) return time;    /* last move before new session     */
969         if(!moves) return increment;     /* current session is incremental   */
970         if(movenr >= 0) movenr -= moves; /* we already finished this session */
971     } while(movenr >= -1);               /* try again for next session       */
972
973     return 0; // no new time quota on this move
974 }
975
976 int
977 ParseTimeControl(tc, ti, mps)
978      char *tc;
979      int ti;
980      int mps;
981 {
982   long tc1;
983   long tc2;
984   char buf[MSG_SIZ];
985   
986   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
987   if(ti > 0) {
988     if(mps)
989       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990     else sprintf(buf, "+%s+%d", tc, ti);
991   } else {
992     if(mps)
993              sprintf(buf, "+%d/%s", mps, tc);
994     else sprintf(buf, "+%s", tc);
995   }
996   fullTimeControlString = StrSave(buf);
997   
998   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
999     return FALSE;
1000   }
1001   
1002   if( *tc == '/' ) {
1003     /* Parse second time control */
1004     tc++;
1005     
1006     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1007       return FALSE;
1008     }
1009     
1010     if( tc2 == 0 ) {
1011       return FALSE;
1012     }
1013     
1014     timeControl_2 = tc2 * 1000;
1015   }
1016   else {
1017     timeControl_2 = 0;
1018   }
1019   
1020   if( tc1 == 0 ) {
1021     return FALSE;
1022   }
1023   
1024   timeControl = tc1 * 1000;
1025   
1026   if (ti >= 0) {
1027     timeIncrement = ti * 1000;  /* convert to ms */
1028     movesPerSession = 0;
1029   } else {
1030     timeIncrement = 0;
1031     movesPerSession = mps;
1032   }
1033   return TRUE;
1034 }
1035
1036 void
1037 InitBackEnd2()
1038 {
1039     if (appData.debugMode) {
1040         fprintf(debugFP, "%s\n", programVersion);
1041     }
1042
1043     if (appData.matchGames > 0) {
1044         appData.matchMode = TRUE;
1045     } else if (appData.matchMode) {
1046         appData.matchGames = 1;
1047     }
1048     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1049         appData.matchGames = appData.sameColorGames;
1050     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1051         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1052         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1053     }
1054     Reset(TRUE, FALSE);
1055     if (appData.noChessProgram || first.protocolVersion == 1) {
1056       InitBackEnd3();
1057     } else {
1058       /* kludge: allow timeout for initial "feature" commands */
1059       FreezeUI();
1060       DisplayMessage("", _("Starting chess program"));
1061       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1062     }
1063 }
1064
1065 void
1066 InitBackEnd3 P((void))
1067 {
1068     GameMode initialMode;
1069     char buf[MSG_SIZ];
1070     int err;
1071
1072     InitChessProgram(&first, startedFromSetupPosition);
1073
1074
1075     if (appData.icsActive) {
1076 #ifdef WIN32
1077         /* [DM] Make a console window if needed [HGM] merged ifs */
1078         ConsoleCreate(); 
1079 #endif
1080         err = establish();
1081         if (err != 0) {
1082             if (*appData.icsCommPort != NULLCHAR) {
1083                 sprintf(buf, _("Could not open comm port %s"),  
1084                         appData.icsCommPort);
1085             } else {
1086                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1087                         appData.icsHost, appData.icsPort);
1088             }
1089             DisplayFatalError(buf, err, 1);
1090             return;
1091         }
1092         SetICSMode();
1093         telnetISR =
1094           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1095         fromUserISR =
1096           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1097     } else if (appData.noChessProgram) {
1098         SetNCPMode();
1099     } else {
1100         SetGNUMode();
1101     }
1102
1103     if (*appData.cmailGameName != NULLCHAR) {
1104         SetCmailMode();
1105         OpenLoopback(&cmailPR);
1106         cmailISR =
1107           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1108     }
1109     
1110     ThawUI();
1111     DisplayMessage("", "");
1112     if (StrCaseCmp(appData.initialMode, "") == 0) {
1113       initialMode = BeginningOfGame;
1114     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1115       initialMode = TwoMachinesPlay;
1116     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1117       initialMode = AnalyzeFile; 
1118     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1119       initialMode = AnalyzeMode;
1120     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1121       initialMode = MachinePlaysWhite;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1123       initialMode = MachinePlaysBlack;
1124     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1125       initialMode = EditGame;
1126     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1127       initialMode = EditPosition;
1128     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1129       initialMode = Training;
1130     } else {
1131       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1132       DisplayFatalError(buf, 0, 2);
1133       return;
1134     }
1135
1136     if (appData.matchMode) {
1137         /* Set up machine vs. machine match */
1138         if (appData.noChessProgram) {
1139             DisplayFatalError(_("Can't have a match with no chess programs"),
1140                               0, 2);
1141             return;
1142         }
1143         matchMode = TRUE;
1144         matchGame = 1;
1145         if (*appData.loadGameFile != NULLCHAR) {
1146             int index = appData.loadGameIndex; // [HGM] autoinc
1147             if(index<0) lastIndex = index = 1;
1148             if (!LoadGameFromFile(appData.loadGameFile,
1149                                   index,
1150                                   appData.loadGameFile, FALSE)) {
1151                 DisplayFatalError(_("Bad game file"), 0, 1);
1152                 return;
1153             }
1154         } else if (*appData.loadPositionFile != NULLCHAR) {
1155             int index = appData.loadPositionIndex; // [HGM] autoinc
1156             if(index<0) lastIndex = index = 1;
1157             if (!LoadPositionFromFile(appData.loadPositionFile,
1158                                       index,
1159                                       appData.loadPositionFile)) {
1160                 DisplayFatalError(_("Bad position file"), 0, 1);
1161                 return;
1162             }
1163         }
1164         TwoMachinesEvent();
1165     } else if (*appData.cmailGameName != NULLCHAR) {
1166         /* Set up cmail mode */
1167         ReloadCmailMsgEvent(TRUE);
1168     } else {
1169         /* Set up other modes */
1170         if (initialMode == AnalyzeFile) {
1171           if (*appData.loadGameFile == NULLCHAR) {
1172             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1173             return;
1174           }
1175         }
1176         if (*appData.loadGameFile != NULLCHAR) {
1177             (void) LoadGameFromFile(appData.loadGameFile,
1178                                     appData.loadGameIndex,
1179                                     appData.loadGameFile, TRUE);
1180         } else if (*appData.loadPositionFile != NULLCHAR) {
1181             (void) LoadPositionFromFile(appData.loadPositionFile,
1182                                         appData.loadPositionIndex,
1183                                         appData.loadPositionFile);
1184             /* [HGM] try to make self-starting even after FEN load */
1185             /* to allow automatic setup of fairy variants with wtm */
1186             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1187                 gameMode = BeginningOfGame;
1188                 setboardSpoiledMachineBlack = 1;
1189             }
1190             /* [HGM] loadPos: make that every new game uses the setup */
1191             /* from file as long as we do not switch variant          */
1192             if(!blackPlaysFirst) { int i;
1193                 startedFromPositionFile = TRUE;
1194                 CopyBoard(filePosition, boards[0]);
1195                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1196             }
1197         }
1198         if (initialMode == AnalyzeMode) {
1199           if (appData.noChessProgram) {
1200             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1201             return;
1202           }
1203           if (appData.icsActive) {
1204             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1205             return;
1206           }
1207           AnalyzeModeEvent();
1208         } else if (initialMode == AnalyzeFile) {
1209           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210           ShowThinkingEvent();
1211           AnalyzeFileEvent();
1212           AnalysisPeriodicEvent(1);
1213         } else if (initialMode == MachinePlaysWhite) {
1214           if (appData.noChessProgram) {
1215             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1216                               0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1221                               0, 2);
1222             return;
1223           }
1224           MachineWhiteEvent();
1225         } else if (initialMode == MachinePlaysBlack) {
1226           if (appData.noChessProgram) {
1227             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1228                               0, 2);
1229             return;
1230           }
1231           if (appData.icsActive) {
1232             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1233                               0, 2);
1234             return;
1235           }
1236           MachineBlackEvent();
1237         } else if (initialMode == TwoMachinesPlay) {
1238           if (appData.noChessProgram) {
1239             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1240                               0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1245                               0, 2);
1246             return;
1247           }
1248           TwoMachinesEvent();
1249         } else if (initialMode == EditGame) {
1250           EditGameEvent();
1251         } else if (initialMode == EditPosition) {
1252           EditPositionEvent();
1253         } else if (initialMode == Training) {
1254           if (*appData.loadGameFile == NULLCHAR) {
1255             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1256             return;
1257           }
1258           TrainingEvent();
1259         }
1260     }
1261 }
1262
1263 /*
1264  * Establish will establish a contact to a remote host.port.
1265  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266  *  used to talk to the host.
1267  * Returns 0 if okay, error code if not.
1268  */
1269 int
1270 establish()
1271 {
1272     char buf[MSG_SIZ];
1273
1274     if (*appData.icsCommPort != NULLCHAR) {
1275         /* Talk to the host through a serial comm port */
1276         return OpenCommPort(appData.icsCommPort, &icsPR);
1277
1278     } else if (*appData.gateway != NULLCHAR) {
1279         if (*appData.remoteShell == NULLCHAR) {
1280             /* Use the rcmd protocol to run telnet program on a gateway host */
1281             snprintf(buf, sizeof(buf), "%s %s %s",
1282                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1283             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1284
1285         } else {
1286             /* Use the rsh program to run telnet program on a gateway host */
1287             if (*appData.remoteUser == NULLCHAR) {
1288                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289                         appData.gateway, appData.telnetProgram,
1290                         appData.icsHost, appData.icsPort);
1291             } else {
1292                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293                         appData.remoteShell, appData.gateway, 
1294                         appData.remoteUser, appData.telnetProgram,
1295                         appData.icsHost, appData.icsPort);
1296             }
1297             return StartChildProcess(buf, "", &icsPR);
1298
1299         }
1300     } else if (appData.useTelnet) {
1301         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1302
1303     } else {
1304         /* TCP socket interface differs somewhat between
1305            Unix and NT; handle details in the front end.
1306            */
1307         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1308     }
1309 }
1310
1311 void
1312 show_bytes(fp, buf, count)
1313      FILE *fp;
1314      char *buf;
1315      int count;
1316 {
1317     while (count--) {
1318         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319             fprintf(fp, "\\%03o", *buf & 0xff);
1320         } else {
1321             putc(*buf, fp);
1322         }
1323         buf++;
1324     }
1325     fflush(fp);
1326 }
1327
1328 /* Returns an errno value */
1329 int
1330 OutputMaybeTelnet(pr, message, count, outError)
1331      ProcRef pr;
1332      char *message;
1333      int count;
1334      int *outError;
1335 {
1336     char buf[8192], *p, *q, *buflim;
1337     int left, newcount, outcount;
1338
1339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340         *appData.gateway != NULLCHAR) {
1341         if (appData.debugMode) {
1342             fprintf(debugFP, ">ICS: ");
1343             show_bytes(debugFP, message, count);
1344             fprintf(debugFP, "\n");
1345         }
1346         return OutputToProcess(pr, message, count, outError);
1347     }
1348
1349     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1350     p = message;
1351     q = buf;
1352     left = count;
1353     newcount = 0;
1354     while (left) {
1355         if (q >= buflim) {
1356             if (appData.debugMode) {
1357                 fprintf(debugFP, ">ICS: ");
1358                 show_bytes(debugFP, buf, newcount);
1359                 fprintf(debugFP, "\n");
1360             }
1361             outcount = OutputToProcess(pr, buf, newcount, outError);
1362             if (outcount < newcount) return -1; /* to be sure */
1363             q = buf;
1364             newcount = 0;
1365         }
1366         if (*p == '\n') {
1367             *q++ = '\r';
1368             newcount++;
1369         } else if (((unsigned char) *p) == TN_IAC) {
1370             *q++ = (char) TN_IAC;
1371             newcount ++;
1372         }
1373         *q++ = *p++;
1374         newcount++;
1375         left--;
1376     }
1377     if (appData.debugMode) {
1378         fprintf(debugFP, ">ICS: ");
1379         show_bytes(debugFP, buf, newcount);
1380         fprintf(debugFP, "\n");
1381     }
1382     outcount = OutputToProcess(pr, buf, newcount, outError);
1383     if (outcount < newcount) return -1; /* to be sure */
1384     return count;
1385 }
1386
1387 void
1388 read_from_player(isr, closure, message, count, error)
1389      InputSourceRef isr;
1390      VOIDSTAR closure;
1391      char *message;
1392      int count;
1393      int error;
1394 {
1395     int outError, outCount;
1396     static int gotEof = 0;
1397
1398     /* Pass data read from player on to ICS */
1399     if (count > 0) {
1400         gotEof = 0;
1401         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402         if (outCount < count) {
1403             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1404         }
1405     } else if (count < 0) {
1406         RemoveInputSource(isr);
1407         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408     } else if (gotEof++ > 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1411     }
1412 }
1413
1414 void
1415 KeepAlive()
1416 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417     SendToICS("date\n");
1418     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1419 }
1420
1421 void
1422 SendToICS(s)
1423      char *s;
1424 {
1425     int count, outCount, outError;
1426
1427     if (icsPR == NULL) return;
1428
1429     count = strlen(s);
1430     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1431     if (outCount < count) {
1432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1433     }
1434 }
1435
1436 /* This is used for sending logon scripts to the ICS. Sending
1437    without a delay causes problems when using timestamp on ICC
1438    (at least on my machine). */
1439 void
1440 SendToICSDelayed(s,msdelay)
1441      char *s;
1442      long msdelay;
1443 {
1444     int count, outCount, outError;
1445
1446     if (icsPR == NULL) return;
1447
1448     count = strlen(s);
1449     if (appData.debugMode) {
1450         fprintf(debugFP, ">ICS: ");
1451         show_bytes(debugFP, s, count);
1452         fprintf(debugFP, "\n");
1453     }
1454     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1455                                       msdelay);
1456     if (outCount < count) {
1457         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1458     }
1459 }
1460
1461
1462 /* Remove all highlighting escape sequences in s
1463    Also deletes any suffix starting with '(' 
1464    */
1465 char *
1466 StripHighlightAndTitle(s)
1467      char *s;
1468 {
1469     static char retbuf[MSG_SIZ];
1470     char *p = retbuf;
1471
1472     while (*s != NULLCHAR) {
1473         while (*s == '\033') {
1474             while (*s != NULLCHAR && !isalpha(*s)) s++;
1475             if (*s != NULLCHAR) s++;
1476         }
1477         while (*s != NULLCHAR && *s != '\033') {
1478             if (*s == '(' || *s == '[') {
1479                 *p = NULLCHAR;
1480                 return retbuf;
1481             }
1482             *p++ = *s++;
1483         }
1484     }
1485     *p = NULLCHAR;
1486     return retbuf;
1487 }
1488
1489 /* Remove all highlighting escape sequences in s */
1490 char *
1491 StripHighlight(s)
1492      char *s;
1493 {
1494     static char retbuf[MSG_SIZ];
1495     char *p = retbuf;
1496
1497     while (*s != NULLCHAR) {
1498         while (*s == '\033') {
1499             while (*s != NULLCHAR && !isalpha(*s)) s++;
1500             if (*s != NULLCHAR) s++;
1501         }
1502         while (*s != NULLCHAR && *s != '\033') {
1503             *p++ = *s++;
1504         }
1505     }
1506     *p = NULLCHAR;
1507     return retbuf;
1508 }
1509
1510 char *variantNames[] = VARIANT_NAMES;
1511 char *
1512 VariantName(v)
1513      VariantClass v;
1514 {
1515     return variantNames[v];
1516 }
1517
1518
1519 /* Identify a variant from the strings the chess servers use or the
1520    PGN Variant tag names we use. */
1521 VariantClass
1522 StringToVariant(e)
1523      char *e;
1524 {
1525     char *p;
1526     int wnum = -1;
1527     VariantClass v = VariantNormal;
1528     int i, found = FALSE;
1529     char buf[MSG_SIZ];
1530
1531     if (!e) return v;
1532
1533     /* [HGM] skip over optional board-size prefixes */
1534     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1535         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1536         while( *e++ != '_');
1537     }
1538
1539     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1540       if (StrCaseStr(e, variantNames[i])) {
1541         v = (VariantClass) i;
1542         found = TRUE;
1543         break;
1544       }
1545     }
1546
1547     if (!found) {
1548       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1549           || StrCaseStr(e, "wild/fr") 
1550           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1551         v = VariantFischeRandom;
1552       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1553                  (i = 1, p = StrCaseStr(e, "w"))) {
1554         p += i;
1555         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1556         if (isdigit(*p)) {
1557           wnum = atoi(p);
1558         } else {
1559           wnum = -1;
1560         }
1561         switch (wnum) {
1562         case 0: /* FICS only, actually */
1563         case 1:
1564           /* Castling legal even if K starts on d-file */
1565           v = VariantWildCastle;
1566           break;
1567         case 2:
1568         case 3:
1569         case 4:
1570           /* Castling illegal even if K & R happen to start in
1571              normal positions. */
1572           v = VariantNoCastle;
1573           break;
1574         case 5:
1575         case 7:
1576         case 8:
1577         case 10:
1578         case 11:
1579         case 12:
1580         case 13:
1581         case 14:
1582         case 15:
1583         case 18:
1584         case 19:
1585           /* Castling legal iff K & R start in normal positions */
1586           v = VariantNormal;
1587           break;
1588         case 6:
1589         case 20:
1590         case 21:
1591           /* Special wilds for position setup; unclear what to do here */
1592           v = VariantLoadable;
1593           break;
1594         case 9:
1595           /* Bizarre ICC game */
1596           v = VariantTwoKings;
1597           break;
1598         case 16:
1599           v = VariantKriegspiel;
1600           break;
1601         case 17:
1602           v = VariantLosers;
1603           break;
1604         case 22:
1605           v = VariantFischeRandom;
1606           break;
1607         case 23:
1608           v = VariantCrazyhouse;
1609           break;
1610         case 24:
1611           v = VariantBughouse;
1612           break;
1613         case 25:
1614           v = Variant3Check;
1615           break;
1616         case 26:
1617           /* Not quite the same as FICS suicide! */
1618           v = VariantGiveaway;
1619           break;
1620         case 27:
1621           v = VariantAtomic;
1622           break;
1623         case 28:
1624           v = VariantShatranj;
1625           break;
1626
1627         /* Temporary names for future ICC types.  The name *will* change in 
1628            the next xboard/WinBoard release after ICC defines it. */
1629         case 29:
1630           v = Variant29;
1631           break;
1632         case 30:
1633           v = Variant30;
1634           break;
1635         case 31:
1636           v = Variant31;
1637           break;
1638         case 32:
1639           v = Variant32;
1640           break;
1641         case 33:
1642           v = Variant33;
1643           break;
1644         case 34:
1645           v = Variant34;
1646           break;
1647         case 35:
1648           v = Variant35;
1649           break;
1650         case 36:
1651           v = Variant36;
1652           break;
1653         case 37:
1654           v = VariantShogi;
1655           break;
1656         case 38:
1657           v = VariantXiangqi;
1658           break;
1659         case 39:
1660           v = VariantCourier;
1661           break;
1662         case 40:
1663           v = VariantGothic;
1664           break;
1665         case 41:
1666           v = VariantCapablanca;
1667           break;
1668         case 42:
1669           v = VariantKnightmate;
1670           break;
1671         case 43:
1672           v = VariantFairy;
1673           break;
1674         case 44:
1675           v = VariantCylinder;
1676           break;
1677         case 45:
1678           v = VariantFalcon;
1679           break;
1680         case 46:
1681           v = VariantCapaRandom;
1682           break;
1683         case 47:
1684           v = VariantBerolina;
1685           break;
1686         case 48:
1687           v = VariantJanus;
1688           break;
1689         case 49:
1690           v = VariantSuper;
1691           break;
1692         case 50:
1693           v = VariantGreat;
1694           break;
1695         case -1:
1696           /* Found "wild" or "w" in the string but no number;
1697              must assume it's normal chess. */
1698           v = VariantNormal;
1699           break;
1700         default:
1701           sprintf(buf, _("Unknown wild type %d"), wnum);
1702           DisplayError(buf, 0);
1703           v = VariantUnknown;
1704           break;
1705         }
1706       }
1707     }
1708     if (appData.debugMode) {
1709       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1710               e, wnum, VariantName(v));
1711     }
1712     return v;
1713 }
1714
1715 static int leftover_start = 0, leftover_len = 0;
1716 char star_match[STAR_MATCH_N][MSG_SIZ];
1717
1718 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1719    advance *index beyond it, and set leftover_start to the new value of
1720    *index; else return FALSE.  If pattern contains the character '*', it
1721    matches any sequence of characters not containing '\r', '\n', or the
1722    character following the '*' (if any), and the matched sequence(s) are
1723    copied into star_match.
1724    */
1725 int
1726 looking_at(buf, index, pattern)
1727      char *buf;
1728      int *index;
1729      char *pattern;
1730 {
1731     char *bufp = &buf[*index], *patternp = pattern;
1732     int star_count = 0;
1733     char *matchp = star_match[0];
1734     
1735     for (;;) {
1736         if (*patternp == NULLCHAR) {
1737             *index = leftover_start = bufp - buf;
1738             *matchp = NULLCHAR;
1739             return TRUE;
1740         }
1741         if (*bufp == NULLCHAR) return FALSE;
1742         if (*patternp == '*') {
1743             if (*bufp == *(patternp + 1)) {
1744                 *matchp = NULLCHAR;
1745                 matchp = star_match[++star_count];
1746                 patternp += 2;
1747                 bufp++;
1748                 continue;
1749             } else if (*bufp == '\n' || *bufp == '\r') {
1750                 patternp++;
1751                 if (*patternp == NULLCHAR)
1752                   continue;
1753                 else
1754                   return FALSE;
1755             } else {
1756                 *matchp++ = *bufp++;
1757                 continue;
1758             }
1759         }
1760         if (*patternp != *bufp) return FALSE;
1761         patternp++;
1762         bufp++;
1763     }
1764 }
1765
1766 void
1767 SendToPlayer(data, length)
1768      char *data;
1769      int length;
1770 {
1771     int error, outCount;
1772     outCount = OutputToProcess(NoProc, data, length, &error);
1773     if (outCount < length) {
1774         DisplayFatalError(_("Error writing to display"), error, 1);
1775     }
1776 }
1777
1778 void
1779 PackHolding(packed, holding)
1780      char packed[];
1781      char *holding;
1782 {
1783     char *p = holding;
1784     char *q = packed;
1785     int runlength = 0;
1786     int curr = 9999;
1787     do {
1788         if (*p == curr) {
1789             runlength++;
1790         } else {
1791             switch (runlength) {
1792               case 0:
1793                 break;
1794               case 1:
1795                 *q++ = curr;
1796                 break;
1797               case 2:
1798                 *q++ = curr;
1799                 *q++ = curr;
1800                 break;
1801               default:
1802                 sprintf(q, "%d", runlength);
1803                 while (*q) q++;
1804                 *q++ = curr;
1805                 break;
1806             }
1807             runlength = 1;
1808             curr = *p;
1809         }
1810     } while (*p++);
1811     *q = NULLCHAR;
1812 }
1813
1814 /* Telnet protocol requests from the front end */
1815 void
1816 TelnetRequest(ddww, option)
1817      unsigned char ddww, option;
1818 {
1819     unsigned char msg[3];
1820     int outCount, outError;
1821
1822     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1823
1824     if (appData.debugMode) {
1825         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1826         switch (ddww) {
1827           case TN_DO:
1828             ddwwStr = "DO";
1829             break;
1830           case TN_DONT:
1831             ddwwStr = "DONT";
1832             break;
1833           case TN_WILL:
1834             ddwwStr = "WILL";
1835             break;
1836           case TN_WONT:
1837             ddwwStr = "WONT";
1838             break;
1839           default:
1840             ddwwStr = buf1;
1841             sprintf(buf1, "%d", ddww);
1842             break;
1843         }
1844         switch (option) {
1845           case TN_ECHO:
1846             optionStr = "ECHO";
1847             break;
1848           default:
1849             optionStr = buf2;
1850             sprintf(buf2, "%d", option);
1851             break;
1852         }
1853         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1854     }
1855     msg[0] = TN_IAC;
1856     msg[1] = ddww;
1857     msg[2] = option;
1858     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1859     if (outCount < 3) {
1860         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1861     }
1862 }
1863
1864 void
1865 DoEcho()
1866 {
1867     if (!appData.icsActive) return;
1868     TelnetRequest(TN_DO, TN_ECHO);
1869 }
1870
1871 void
1872 DontEcho()
1873 {
1874     if (!appData.icsActive) return;
1875     TelnetRequest(TN_DONT, TN_ECHO);
1876 }
1877
1878 void
1879 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1880 {
1881     /* put the holdings sent to us by the server on the board holdings area */
1882     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1883     char p;
1884     ChessSquare piece;
1885
1886     if(gameInfo.holdingsWidth < 2)  return;
1887
1888     if( (int)lowestPiece >= BlackPawn ) {
1889         holdingsColumn = 0;
1890         countsColumn = 1;
1891         holdingsStartRow = BOARD_HEIGHT-1;
1892         direction = -1;
1893     } else {
1894         holdingsColumn = BOARD_WIDTH-1;
1895         countsColumn = BOARD_WIDTH-2;
1896         holdingsStartRow = 0;
1897         direction = 1;
1898     }
1899
1900     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1901         board[i][holdingsColumn] = EmptySquare;
1902         board[i][countsColumn]   = (ChessSquare) 0;
1903     }
1904     while( (p=*holdings++) != NULLCHAR ) {
1905         piece = CharToPiece( ToUpper(p) );
1906         if(piece == EmptySquare) continue;
1907         /*j = (int) piece - (int) WhitePawn;*/
1908         j = PieceToNumber(piece);
1909         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1910         if(j < 0) continue;               /* should not happen */
1911         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1912         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1913         board[holdingsStartRow+j*direction][countsColumn]++;
1914     }
1915
1916 }
1917
1918
1919 void
1920 VariantSwitch(Board board, VariantClass newVariant)
1921 {
1922    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1923    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1924 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1925
1926    startedFromPositionFile = FALSE;
1927    if(gameInfo.variant == newVariant) return;
1928
1929    /* [HGM] This routine is called each time an assignment is made to
1930     * gameInfo.variant during a game, to make sure the board sizes
1931     * are set to match the new variant. If that means adding or deleting
1932     * holdings, we shift the playing board accordingly
1933     * This kludge is needed because in ICS observe mode, we get boards
1934     * of an ongoing game without knowing the variant, and learn about the
1935     * latter only later. This can be because of the move list we requested,
1936     * in which case the game history is refilled from the beginning anyway,
1937     * but also when receiving holdings of a crazyhouse game. In the latter
1938     * case we want to add those holdings to the already received position.
1939     */
1940
1941
1942   if (appData.debugMode) {
1943     fprintf(debugFP, "Switch board from %s to %s\n",
1944                VariantName(gameInfo.variant), VariantName(newVariant));
1945     setbuf(debugFP, NULL);
1946   }
1947     shuffleOpenings = 0;       /* [HGM] shuffle */
1948     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1949     switch(newVariant) {
1950             case VariantShogi:
1951               newWidth = 9;  newHeight = 9;
1952               gameInfo.holdingsSize = 7;
1953             case VariantBughouse:
1954             case VariantCrazyhouse:
1955               newHoldingsWidth = 2; break;
1956             default:
1957               newHoldingsWidth = gameInfo.holdingsSize = 0;
1958     }
1959
1960     if(newWidth  != gameInfo.boardWidth  ||
1961        newHeight != gameInfo.boardHeight ||
1962        newHoldingsWidth != gameInfo.holdingsWidth ) {
1963
1964         /* shift position to new playing area, if needed */
1965         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1966            for(i=0; i<BOARD_HEIGHT; i++) 
1967                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1968                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1969                                                      board[i][j];
1970            for(i=0; i<newHeight; i++) {
1971                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1972                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1973            }
1974         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1975            for(i=0; i<BOARD_HEIGHT; i++)
1976                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1977                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1978                                                  board[i][j];
1979         }
1980
1981         gameInfo.boardWidth  = newWidth;
1982         gameInfo.boardHeight = newHeight;
1983         gameInfo.holdingsWidth = newHoldingsWidth;
1984         gameInfo.variant = newVariant;
1985         InitDrawingSizes(-2, 0);
1986
1987         /* [HGM] The following should definitely be solved in a better way */
1988         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
1989     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
1990
1991     forwardMostMove = oldForwardMostMove;
1992     backwardMostMove = oldBackwardMostMove;
1993     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
1994 }
1995
1996 static int loggedOn = FALSE;
1997
1998 /*-- Game start info cache: --*/
1999 int gs_gamenum;
2000 char gs_kind[MSG_SIZ];
2001 static char player1Name[128] = "";
2002 static char player2Name[128] = "";
2003 static int player1Rating = -1;
2004 static int player2Rating = -1;
2005 /*----------------------------*/
2006
2007 ColorClass curColor = ColorNormal;
2008 int suppressKibitz = 0;
2009
2010 void
2011 read_from_ics(isr, closure, data, count, error)
2012      InputSourceRef isr;
2013      VOIDSTAR closure;
2014      char *data;
2015      int count;
2016      int error;
2017 {
2018 #define BUF_SIZE 8192
2019 #define STARTED_NONE 0
2020 #define STARTED_MOVES 1
2021 #define STARTED_BOARD 2
2022 #define STARTED_OBSERVE 3
2023 #define STARTED_HOLDINGS 4
2024 #define STARTED_CHATTER 5
2025 #define STARTED_COMMENT 6
2026 #define STARTED_MOVES_NOHIDE 7
2027     
2028     static int started = STARTED_NONE;
2029     static char parse[20000];
2030     static int parse_pos = 0;
2031     static char buf[BUF_SIZE + 1];
2032     static int firstTime = TRUE, intfSet = FALSE;
2033     static ColorClass prevColor = ColorNormal;
2034     static int savingComment = FALSE;
2035     char str[500];
2036     int i, oldi;
2037     int buf_len;
2038     int next_out;
2039     int tkind;
2040     int backup;    /* [DM] For zippy color lines */
2041     char *p;
2042     char talker[MSG_SIZ]; // [HGM] chat
2043     int channel;
2044
2045     if (appData.debugMode) {
2046       if (!error) {
2047         fprintf(debugFP, "<ICS: ");
2048         show_bytes(debugFP, data, count);
2049         fprintf(debugFP, "\n");
2050       }
2051     }
2052
2053     if (appData.debugMode) { int f = forwardMostMove;
2054         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2055                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2056     }
2057     if (count > 0) {
2058         /* If last read ended with a partial line that we couldn't parse,
2059            prepend it to the new read and try again. */
2060         if (leftover_len > 0) {
2061             for (i=0; i<leftover_len; i++)
2062               buf[i] = buf[leftover_start + i];
2063         }
2064
2065         /* Copy in new characters, removing nulls and \r's */
2066         buf_len = leftover_len;
2067         for (i = 0; i < count; i++) {
2068             if (data[i] != NULLCHAR && data[i] != '\r')
2069               buf[buf_len++] = data[i];
2070             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2071                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2072                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2073                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2074                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2075             }
2076         }
2077
2078         buf[buf_len] = NULLCHAR;
2079         next_out = leftover_len;
2080         leftover_start = 0;
2081         
2082         i = 0;
2083         while (i < buf_len) {
2084             /* Deal with part of the TELNET option negotiation
2085                protocol.  We refuse to do anything beyond the
2086                defaults, except that we allow the WILL ECHO option,
2087                which ICS uses to turn off password echoing when we are
2088                directly connected to it.  We reject this option
2089                if localLineEditing mode is on (always on in xboard)
2090                and we are talking to port 23, which might be a real
2091                telnet server that will try to keep WILL ECHO on permanently.
2092              */
2093             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2094                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2095                 unsigned char option;
2096                 oldi = i;
2097                 switch ((unsigned char) buf[++i]) {
2098                   case TN_WILL:
2099                     if (appData.debugMode)
2100                       fprintf(debugFP, "\n<WILL ");
2101                     switch (option = (unsigned char) buf[++i]) {
2102                       case TN_ECHO:
2103                         if (appData.debugMode)
2104                           fprintf(debugFP, "ECHO ");
2105                         /* Reply only if this is a change, according
2106                            to the protocol rules. */
2107                         if (remoteEchoOption) break;
2108                         if (appData.localLineEditing &&
2109                             atoi(appData.icsPort) == TN_PORT) {
2110                             TelnetRequest(TN_DONT, TN_ECHO);
2111                         } else {
2112                             EchoOff();
2113                             TelnetRequest(TN_DO, TN_ECHO);
2114                             remoteEchoOption = TRUE;
2115                         }
2116                         break;
2117                       default:
2118                         if (appData.debugMode)
2119                           fprintf(debugFP, "%d ", option);
2120                         /* Whatever this is, we don't want it. */
2121                         TelnetRequest(TN_DONT, option);
2122                         break;
2123                     }
2124                     break;
2125                   case TN_WONT:
2126                     if (appData.debugMode)
2127                       fprintf(debugFP, "\n<WONT ");
2128                     switch (option = (unsigned char) buf[++i]) {
2129                       case TN_ECHO:
2130                         if (appData.debugMode)
2131                           fprintf(debugFP, "ECHO ");
2132                         /* Reply only if this is a change, according
2133                            to the protocol rules. */
2134                         if (!remoteEchoOption) break;
2135                         EchoOn();
2136                         TelnetRequest(TN_DONT, TN_ECHO);
2137                         remoteEchoOption = FALSE;
2138                         break;
2139                       default:
2140                         if (appData.debugMode)
2141                           fprintf(debugFP, "%d ", (unsigned char) option);
2142                         /* Whatever this is, it must already be turned
2143                            off, because we never agree to turn on
2144                            anything non-default, so according to the
2145                            protocol rules, we don't reply. */
2146                         break;
2147                     }
2148                     break;
2149                   case TN_DO:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<DO ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       default:
2154                         /* Whatever this is, we refuse to do it. */
2155                         if (appData.debugMode)
2156                           fprintf(debugFP, "%d ", option);
2157                         TelnetRequest(TN_WONT, option);
2158                         break;
2159                     }
2160                     break;
2161                   case TN_DONT:
2162                     if (appData.debugMode)
2163                       fprintf(debugFP, "\n<DONT ");
2164                     switch (option = (unsigned char) buf[++i]) {
2165                       default:
2166                         if (appData.debugMode)
2167                           fprintf(debugFP, "%d ", option);
2168                         /* Whatever this is, we are already not doing
2169                            it, because we never agree to do anything
2170                            non-default, so according to the protocol
2171                            rules, we don't reply. */
2172                         break;
2173                     }
2174                     break;
2175                   case TN_IAC:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<IAC ");
2178                     /* Doubled IAC; pass it through */
2179                     i--;
2180                     break;
2181                   default:
2182                     if (appData.debugMode)
2183                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2184                     /* Drop all other telnet commands on the floor */
2185                     break;
2186                 }
2187                 if (oldi > next_out)
2188                   SendToPlayer(&buf[next_out], oldi - next_out);
2189                 if (++i > next_out)
2190                   next_out = i;
2191                 continue;
2192             }
2193                 
2194             /* OK, this at least will *usually* work */
2195             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2196                 loggedOn = TRUE;
2197             }
2198             
2199             if (loggedOn && !intfSet) {
2200                 if (ics_type == ICS_ICC) {
2201                   sprintf(str,
2202                           "/set-quietly interface %s\n/set-quietly style 12\n",
2203                           programVersion);
2204
2205                 } else if (ics_type == ICS_CHESSNET) {
2206                   sprintf(str, "/style 12\n");
2207                 } else {
2208                   strcpy(str, "alias $ @\n$set interface ");
2209                   strcat(str, programVersion);
2210                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2211 #ifdef WIN32
2212                   strcat(str, "$iset nohighlight 1\n");
2213 #endif
2214                   strcat(str, "$iset lock 1\n$style 12\n");
2215                 }
2216                 SendToICS(str);
2217                 intfSet = TRUE;
2218             }
2219
2220             if (started == STARTED_COMMENT) {
2221                 /* Accumulate characters in comment */
2222                 parse[parse_pos++] = buf[i];
2223                 if (buf[i] == '\n') {
2224                     parse[parse_pos] = NULLCHAR;
2225                     if(chattingPartner>=0) {
2226                         char mess[MSG_SIZ];
2227                         sprintf(mess, "%s%s", talker, parse);
2228                         OutputChatMessage(chattingPartner, mess);
2229                         chattingPartner = -1;
2230                     } else
2231                     if(!suppressKibitz) // [HGM] kibitz
2232                         AppendComment(forwardMostMove, StripHighlight(parse));
2233                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2234                         int nrDigit = 0, nrAlph = 0, i;
2235                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2236                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2237                         parse[parse_pos] = NULLCHAR;
2238                         // try to be smart: if it does not look like search info, it should go to
2239                         // ICS interaction window after all, not to engine-output window.
2240                         for(i=0; i<parse_pos; i++) { // count letters and digits
2241                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2242                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2243                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2244                         }
2245                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2246                             int depth=0; float score;
2247                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2248                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2249                                 pvInfoList[forwardMostMove-1].depth = depth;
2250                                 pvInfoList[forwardMostMove-1].score = 100*score;
2251                             }
2252                             OutputKibitz(suppressKibitz, parse);
2253                         } else {
2254                             char tmp[MSG_SIZ];
2255                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2256                             SendToPlayer(tmp, strlen(tmp));
2257                         }
2258                     }
2259                     started = STARTED_NONE;
2260                 } else {
2261                     /* Don't match patterns against characters in chatter */
2262                     i++;
2263                     continue;
2264                 }
2265             }
2266             if (started == STARTED_CHATTER) {
2267                 if (buf[i] != '\n') {
2268                     /* Don't match patterns against characters in chatter */
2269                     i++;
2270                     continue;
2271                 }
2272                 started = STARTED_NONE;
2273             }
2274
2275             /* Kludge to deal with rcmd protocol */
2276             if (firstTime && looking_at(buf, &i, "\001*")) {
2277                 DisplayFatalError(&buf[1], 0, 1);
2278                 continue;
2279             } else {
2280                 firstTime = FALSE;
2281             }
2282
2283             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2284                 ics_type = ICS_ICC;
2285                 ics_prefix = "/";
2286                 if (appData.debugMode)
2287                   fprintf(debugFP, "ics_type %d\n", ics_type);
2288                 continue;
2289             }
2290             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2291                 ics_type = ICS_FICS;
2292                 ics_prefix = "$";
2293                 if (appData.debugMode)
2294                   fprintf(debugFP, "ics_type %d\n", ics_type);
2295                 continue;
2296             }
2297             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2298                 ics_type = ICS_CHESSNET;
2299                 ics_prefix = "/";
2300                 if (appData.debugMode)
2301                   fprintf(debugFP, "ics_type %d\n", ics_type);
2302                 continue;
2303             }
2304
2305             if (!loggedOn &&
2306                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2307                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2308                  looking_at(buf, &i, "will be \"*\""))) {
2309               strcpy(ics_handle, star_match[0]);
2310               continue;
2311             }
2312
2313             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2314               char buf[MSG_SIZ];
2315               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2316               DisplayIcsInteractionTitle(buf);
2317               have_set_title = TRUE;
2318             }
2319
2320             /* skip finger notes */
2321             if (started == STARTED_NONE &&
2322                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2323                  (buf[i] == '1' && buf[i+1] == '0')) &&
2324                 buf[i+2] == ':' && buf[i+3] == ' ') {
2325               started = STARTED_CHATTER;
2326               i += 3;
2327               continue;
2328             }
2329
2330             /* skip formula vars */
2331             if (started == STARTED_NONE &&
2332                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2333               started = STARTED_CHATTER;
2334               i += 3;
2335               continue;
2336             }
2337
2338             oldi = i;
2339             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2340             if (appData.autoKibitz && started == STARTED_NONE && 
2341                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2342                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2343                 if(looking_at(buf, &i, "* kibitzes: ") &&
2344                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2345                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2346                         suppressKibitz = TRUE;
2347                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2348                                 && (gameMode == IcsPlayingWhite)) ||
2349                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2350                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2351                             started = STARTED_CHATTER; // own kibitz we simply discard
2352                         else {
2353                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2354                             parse_pos = 0; parse[0] = NULLCHAR;
2355                             savingComment = TRUE;
2356                             suppressKibitz = gameMode != IcsObserving ? 2 :
2357                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2358                         } 
2359                         continue;
2360                 } else
2361                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2362                     started = STARTED_CHATTER;
2363                     suppressKibitz = TRUE;
2364                 }
2365             } // [HGM] kibitz: end of patch
2366
2367 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2368
2369             // [HGM] chat: intercept tells by users for which we have an open chat window
2370             channel = -1;
2371             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2372                                            looking_at(buf, &i, "* whispers:") ||
2373                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2374                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2375                 int p;
2376                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2377                 chattingPartner = -1;
2378
2379                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2380                 for(p=0; p<MAX_CHAT; p++) {
2381                     if(channel == atoi(chatPartner[p])) {
2382                     talker[0] = '['; strcat(talker, "]");
2383                     chattingPartner = p; break;
2384                     }
2385                 } else
2386                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2387                 for(p=0; p<MAX_CHAT; p++) {
2388                     if(!strcmp("WHISPER", chatPartner[p])) {
2389                         talker[0] = '['; strcat(talker, "]");
2390                         chattingPartner = p; break;
2391                     }
2392                 }
2393                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2394                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2395                     talker[0] = 0;
2396                     chattingPartner = p; break;
2397                 }
2398                 if(chattingPartner<0) i = oldi; else {
2399                     started = STARTED_COMMENT;
2400                     parse_pos = 0; parse[0] = NULLCHAR;
2401                     savingComment = TRUE;
2402                     suppressKibitz = TRUE;
2403                 }
2404             } // [HGM] chat: end of patch
2405
2406             if (appData.zippyTalk || appData.zippyPlay) {
2407                 /* [DM] Backup address for color zippy lines */
2408                 backup = i;
2409 #if ZIPPY
2410        #ifdef WIN32
2411                if (loggedOn == TRUE)
2412                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2413                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2414        #else
2415                 if (ZippyControl(buf, &i) ||
2416                     ZippyConverse(buf, &i) ||
2417                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2418                       loggedOn = TRUE;
2419                       if (!appData.colorize) continue;
2420                 }
2421        #endif
2422 #endif
2423             } // [DM] 'else { ' deleted
2424                 if (
2425                     /* Regular tells and says */
2426                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2427                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2428                     looking_at(buf, &i, "* says: ") ||
2429                     /* Don't color "message" or "messages" output */
2430                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2431                     looking_at(buf, &i, "*. * at *:*: ") ||
2432                     looking_at(buf, &i, "--* (*:*): ") ||
2433                     /* Message notifications (same color as tells) */
2434                     looking_at(buf, &i, "* has left a message ") ||
2435                     looking_at(buf, &i, "* just sent you a message:\n") ||
2436                     /* Whispers and kibitzes */
2437                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2438                     looking_at(buf, &i, "* kibitzes: ") ||
2439                     /* Channel tells */
2440                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2441
2442                   if (tkind == 1 && strchr(star_match[0], ':')) {
2443                       /* Avoid "tells you:" spoofs in channels */
2444                      tkind = 3;
2445                   }
2446                   if (star_match[0][0] == NULLCHAR ||
2447                       strchr(star_match[0], ' ') ||
2448                       (tkind == 3 && strchr(star_match[1], ' '))) {
2449                     /* Reject bogus matches */
2450                     i = oldi;
2451                   } else {
2452                     if (appData.colorize) {
2453                       if (oldi > next_out) {
2454                         SendToPlayer(&buf[next_out], oldi - next_out);
2455                         next_out = oldi;
2456                       }
2457                       switch (tkind) {
2458                       case 1:
2459                         Colorize(ColorTell, FALSE);
2460                         curColor = ColorTell;
2461                         break;
2462                       case 2:
2463                         Colorize(ColorKibitz, FALSE);
2464                         curColor = ColorKibitz;
2465                         break;
2466                       case 3:
2467                         p = strrchr(star_match[1], '(');
2468                         if (p == NULL) {
2469                           p = star_match[1];
2470                         } else {
2471                           p++;
2472                         }
2473                         if (atoi(p) == 1) {
2474                           Colorize(ColorChannel1, FALSE);
2475                           curColor = ColorChannel1;
2476                         } else {
2477                           Colorize(ColorChannel, FALSE);
2478                           curColor = ColorChannel;
2479                         }
2480                         break;
2481                       case 5:
2482                         curColor = ColorNormal;
2483                         break;
2484                       }
2485                     }
2486                     if (started == STARTED_NONE && appData.autoComment &&
2487                         (gameMode == IcsObserving ||
2488                          gameMode == IcsPlayingWhite ||
2489                          gameMode == IcsPlayingBlack)) {
2490                       parse_pos = i - oldi;
2491                       memcpy(parse, &buf[oldi], parse_pos);
2492                       parse[parse_pos] = NULLCHAR;
2493                       started = STARTED_COMMENT;
2494                       savingComment = TRUE;
2495                     } else {
2496                       started = STARTED_CHATTER;
2497                       savingComment = FALSE;
2498                     }
2499                     loggedOn = TRUE;
2500                     continue;
2501                   }
2502                 }
2503
2504                 if (looking_at(buf, &i, "* s-shouts: ") ||
2505                     looking_at(buf, &i, "* c-shouts: ")) {
2506                     if (appData.colorize) {
2507                         if (oldi > next_out) {
2508                             SendToPlayer(&buf[next_out], oldi - next_out);
2509                             next_out = oldi;
2510                         }
2511                         Colorize(ColorSShout, FALSE);
2512                         curColor = ColorSShout;
2513                     }
2514                     loggedOn = TRUE;
2515                     started = STARTED_CHATTER;
2516                     continue;
2517                 }
2518
2519                 if (looking_at(buf, &i, "--->")) {
2520                     loggedOn = TRUE;
2521                     continue;
2522                 }
2523
2524                 if (looking_at(buf, &i, "* shouts: ") ||
2525                     looking_at(buf, &i, "--> ")) {
2526                     if (appData.colorize) {
2527                         if (oldi > next_out) {
2528                             SendToPlayer(&buf[next_out], oldi - next_out);
2529                             next_out = oldi;
2530                         }
2531                         Colorize(ColorShout, FALSE);
2532                         curColor = ColorShout;
2533                     }
2534                     loggedOn = TRUE;
2535                     started = STARTED_CHATTER;
2536                     continue;
2537                 }
2538
2539                 if (looking_at( buf, &i, "Challenge:")) {
2540                     if (appData.colorize) {
2541                         if (oldi > next_out) {
2542                             SendToPlayer(&buf[next_out], oldi - next_out);
2543                             next_out = oldi;
2544                         }
2545                         Colorize(ColorChallenge, FALSE);
2546                         curColor = ColorChallenge;
2547                     }
2548                     loggedOn = TRUE;
2549                     continue;
2550                 }
2551
2552                 if (looking_at(buf, &i, "* offers you") ||
2553                     looking_at(buf, &i, "* offers to be") ||
2554                     looking_at(buf, &i, "* would like to") ||
2555                     looking_at(buf, &i, "* requests to") ||
2556                     looking_at(buf, &i, "Your opponent offers") ||
2557                     looking_at(buf, &i, "Your opponent requests")) {
2558
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorRequest, FALSE);
2565                         curColor = ColorRequest;
2566                     }
2567                     continue;
2568                 }
2569
2570                 if (looking_at(buf, &i, "* (*) seeking")) {
2571                     if (appData.colorize) {
2572                         if (oldi > next_out) {
2573                             SendToPlayer(&buf[next_out], oldi - next_out);
2574                             next_out = oldi;
2575                         }
2576                         Colorize(ColorSeek, FALSE);
2577                         curColor = ColorSeek;
2578                     }
2579                     continue;
2580             }
2581
2582             if (looking_at(buf, &i, "\\   ")) {
2583                 if (prevColor != ColorNormal) {
2584                     if (oldi > next_out) {
2585                         SendToPlayer(&buf[next_out], oldi - next_out);
2586                         next_out = oldi;
2587                     }
2588                     Colorize(prevColor, TRUE);
2589                     curColor = prevColor;
2590                 }
2591                 if (savingComment) {
2592                     parse_pos = i - oldi;
2593                     memcpy(parse, &buf[oldi], parse_pos);
2594                     parse[parse_pos] = NULLCHAR;
2595                     started = STARTED_COMMENT;
2596                 } else {
2597                     started = STARTED_CHATTER;
2598                 }
2599                 continue;
2600             }
2601
2602             if (looking_at(buf, &i, "Black Strength :") ||
2603                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2604                 looking_at(buf, &i, "<10>") ||
2605                 looking_at(buf, &i, "#@#")) {
2606                 /* Wrong board style */
2607                 loggedOn = TRUE;
2608                 SendToICS(ics_prefix);
2609                 SendToICS("set style 12\n");
2610                 SendToICS(ics_prefix);
2611                 SendToICS("refresh\n");
2612                 continue;
2613             }
2614             
2615             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2616                 ICSInitScript();
2617                 have_sent_ICS_logon = 1;
2618                 continue;
2619             }
2620               
2621             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2622                 (looking_at(buf, &i, "\n<12> ") ||
2623                  looking_at(buf, &i, "<12> "))) {
2624                 loggedOn = TRUE;
2625                 if (oldi > next_out) {
2626                     SendToPlayer(&buf[next_out], oldi - next_out);
2627                 }
2628                 next_out = i;
2629                 started = STARTED_BOARD;
2630                 parse_pos = 0;
2631                 continue;
2632             }
2633
2634             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2635                 looking_at(buf, &i, "<b1> ")) {
2636                 if (oldi > next_out) {
2637                     SendToPlayer(&buf[next_out], oldi - next_out);
2638                 }
2639                 next_out = i;
2640                 started = STARTED_HOLDINGS;
2641                 parse_pos = 0;
2642                 continue;
2643             }
2644
2645             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2646                 loggedOn = TRUE;
2647                 /* Header for a move list -- first line */
2648
2649                 switch (ics_getting_history) {
2650                   case H_FALSE:
2651                     switch (gameMode) {
2652                       case IcsIdle:
2653                       case BeginningOfGame:
2654                         /* User typed "moves" or "oldmoves" while we
2655                            were idle.  Pretend we asked for these
2656                            moves and soak them up so user can step
2657                            through them and/or save them.
2658                            */
2659                         Reset(FALSE, TRUE);
2660                         gameMode = IcsObserving;
2661                         ModeHighlight();
2662                         ics_gamenum = -1;
2663                         ics_getting_history = H_GOT_UNREQ_HEADER;
2664                         break;
2665                       case EditGame: /*?*/
2666                       case EditPosition: /*?*/
2667                         /* Should above feature work in these modes too? */
2668                         /* For now it doesn't */
2669                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2670                         break;
2671                       default:
2672                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2673                         break;
2674                     }
2675                     break;
2676                   case H_REQUESTED:
2677                     /* Is this the right one? */
2678                     if (gameInfo.white && gameInfo.black &&
2679                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2680                         strcmp(gameInfo.black, star_match[2]) == 0) {
2681                         /* All is well */
2682                         ics_getting_history = H_GOT_REQ_HEADER;
2683                     }
2684                     break;
2685                   case H_GOT_REQ_HEADER:
2686                   case H_GOT_UNREQ_HEADER:
2687                   case H_GOT_UNWANTED_HEADER:
2688                   case H_GETTING_MOVES:
2689                     /* Should not happen */
2690                     DisplayError(_("Error gathering move list: two headers"), 0);
2691                     ics_getting_history = H_FALSE;
2692                     break;
2693                 }
2694
2695                 /* Save player ratings into gameInfo if needed */
2696                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2697                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2698                     (gameInfo.whiteRating == -1 ||
2699                      gameInfo.blackRating == -1)) {
2700
2701                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2702                     gameInfo.blackRating = string_to_rating(star_match[3]);
2703                     if (appData.debugMode)
2704                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2705                               gameInfo.whiteRating, gameInfo.blackRating);
2706                 }
2707                 continue;
2708             }
2709
2710             if (looking_at(buf, &i,
2711               "* * match, initial time: * minute*, increment: * second")) {
2712                 /* Header for a move list -- second line */
2713                 /* Initial board will follow if this is a wild game */
2714                 if (gameInfo.event != NULL) free(gameInfo.event);
2715                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2716                 gameInfo.event = StrSave(str);
2717                 /* [HGM] we switched variant. Translate boards if needed. */
2718                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "Move  ")) {
2723                 /* Beginning of a move list */
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     /* Normally should not happen */
2727                     /* Maybe user hit reset while we were parsing */
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Happens if we are ignoring a move list that is not
2731                      * the one we just requested.  Common if the user
2732                      * tries to observe two games without turning off
2733                      * getMoveList */
2734                     break;
2735                   case H_GETTING_MOVES:
2736                     /* Should not happen */
2737                     DisplayError(_("Error gathering move list: nested"), 0);
2738                     ics_getting_history = H_FALSE;
2739                     break;
2740                   case H_GOT_REQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES;
2743                     parse_pos = 0;
2744                     if (oldi > next_out) {
2745                         SendToPlayer(&buf[next_out], oldi - next_out);
2746                     }
2747                     break;
2748                   case H_GOT_UNREQ_HEADER:
2749                     ics_getting_history = H_GETTING_MOVES;
2750                     started = STARTED_MOVES_NOHIDE;
2751                     parse_pos = 0;
2752                     break;
2753                   case H_GOT_UNWANTED_HEADER:
2754                     ics_getting_history = H_FALSE;
2755                     break;
2756                 }
2757                 continue;
2758             }                           
2759             
2760             if (looking_at(buf, &i, "% ") ||
2761                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2762                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2763                 savingComment = FALSE;
2764                 switch (started) {
2765                   case STARTED_MOVES:
2766                   case STARTED_MOVES_NOHIDE:
2767                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2768                     parse[parse_pos + i - oldi] = NULLCHAR;
2769                     ParseGameHistory(parse);
2770 #if ZIPPY
2771                     if (appData.zippyPlay && first.initDone) {
2772                         FeedMovesToProgram(&first, forwardMostMove);
2773                         if (gameMode == IcsPlayingWhite) {
2774                             if (WhiteOnMove(forwardMostMove)) {
2775                                 if (first.sendTime) {
2776                                   if (first.useColors) {
2777                                     SendToProgram("black\n", &first); 
2778                                   }
2779                                   SendTimeRemaining(&first, TRUE);
2780                                 }
2781                                 if (first.useColors) {
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783                                 }
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785                                 first.maybeThinking = TRUE;
2786                             } else {
2787                                 if (first.usePlayother) {
2788                                   if (first.sendTime) {
2789                                     SendTimeRemaining(&first, TRUE);
2790                                   }
2791                                   SendToProgram("playother\n", &first);
2792                                   firstMove = FALSE;
2793                                 } else {
2794                                   firstMove = TRUE;
2795                                 }
2796                             }
2797                         } else if (gameMode == IcsPlayingBlack) {
2798                             if (!WhiteOnMove(forwardMostMove)) {
2799                                 if (first.sendTime) {
2800                                   if (first.useColors) {
2801                                     SendToProgram("white\n", &first);
2802                                   }
2803                                   SendTimeRemaining(&first, FALSE);
2804                                 }
2805                                 if (first.useColors) {
2806                                   SendToProgram("black\n", &first);
2807                                 }
2808                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2809                                 first.maybeThinking = TRUE;
2810                             } else {
2811                                 if (first.usePlayother) {
2812                                   if (first.sendTime) {
2813                                     SendTimeRemaining(&first, FALSE);
2814                                   }
2815                                   SendToProgram("playother\n", &first);
2816                                   firstMove = FALSE;
2817                                 } else {
2818                                   firstMove = TRUE;
2819                                 }
2820                             }
2821                         }                       
2822                     }
2823 #endif
2824                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2825                         /* Moves came from oldmoves or moves command
2826                            while we weren't doing anything else.
2827                            */
2828                         currentMove = forwardMostMove;
2829                         ClearHighlights();/*!!could figure this out*/
2830                         flipView = appData.flipView;
2831                         DrawPosition(FALSE, boards[currentMove]);
2832                         DisplayBothClocks();
2833                         sprintf(str, "%s vs. %s",
2834                                 gameInfo.white, gameInfo.black);
2835                         DisplayTitle(str);
2836                         gameMode = IcsIdle;
2837                     } else {
2838                         /* Moves were history of an active game */
2839                         if (gameInfo.resultDetails != NULL) {
2840                             free(gameInfo.resultDetails);
2841                             gameInfo.resultDetails = NULL;
2842                         }
2843                     }
2844                     HistorySet(parseList, backwardMostMove,
2845                                forwardMostMove, currentMove-1);
2846                     DisplayMove(currentMove - 1);
2847                     if (started == STARTED_MOVES) next_out = i;
2848                     started = STARTED_NONE;
2849                     ics_getting_history = H_FALSE;
2850                     break;
2851
2852                   case STARTED_OBSERVE:
2853                     started = STARTED_NONE;
2854                     SendToICS(ics_prefix);
2855                     SendToICS("refresh\n");
2856                     break;
2857
2858                   default:
2859                     break;
2860                 }
2861                 if(bookHit) { // [HGM] book: simulate book reply
2862                     static char bookMove[MSG_SIZ]; // a bit generous?
2863
2864                     programStats.nodes = programStats.depth = programStats.time = 
2865                     programStats.score = programStats.got_only_move = 0;
2866                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2867
2868                     strcpy(bookMove, "move ");
2869                     strcat(bookMove, bookHit);
2870                     HandleMachineMove(bookMove, &first);
2871                 }
2872                 continue;
2873             }
2874             
2875             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2876                  started == STARTED_HOLDINGS ||
2877                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2878                 /* Accumulate characters in move list or board */
2879                 parse[parse_pos++] = buf[i];
2880             }
2881             
2882             /* Start of game messages.  Mostly we detect start of game
2883                when the first board image arrives.  On some versions
2884                of the ICS, though, we need to do a "refresh" after starting
2885                to observe in order to get the current board right away. */
2886             if (looking_at(buf, &i, "Adding game * to observation list")) {
2887                 started = STARTED_OBSERVE;
2888                 continue;
2889             }
2890
2891             /* Handle auto-observe */
2892             if (appData.autoObserve &&
2893                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2894                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2895                 char *player;
2896                 /* Choose the player that was highlighted, if any. */
2897                 if (star_match[0][0] == '\033' ||
2898                     star_match[1][0] != '\033') {
2899                     player = star_match[0];
2900                 } else {
2901                     player = star_match[2];
2902                 }
2903                 sprintf(str, "%sobserve %s\n",
2904                         ics_prefix, StripHighlightAndTitle(player));
2905                 SendToICS(str);
2906
2907                 /* Save ratings from notify string */
2908                 strcpy(player1Name, star_match[0]);
2909                 player1Rating = string_to_rating(star_match[1]);
2910                 strcpy(player2Name, star_match[2]);
2911                 player2Rating = string_to_rating(star_match[3]);
2912
2913                 if (appData.debugMode)
2914                   fprintf(debugFP, 
2915                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2916                           player1Name, player1Rating,
2917                           player2Name, player2Rating);
2918
2919                 continue;
2920             }
2921
2922             /* Deal with automatic examine mode after a game,
2923                and with IcsObserving -> IcsExamining transition */
2924             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2925                 looking_at(buf, &i, "has made you an examiner of game *")) {
2926
2927                 int gamenum = atoi(star_match[0]);
2928                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2929                     gamenum == ics_gamenum) {
2930                     /* We were already playing or observing this game;
2931                        no need to refetch history */
2932                     gameMode = IcsExamining;
2933                     if (pausing) {
2934                         pauseExamForwardMostMove = forwardMostMove;
2935                     } else if (currentMove < forwardMostMove) {
2936                         ForwardInner(forwardMostMove);
2937                     }
2938                 } else {
2939                     /* I don't think this case really can happen */
2940                     SendToICS(ics_prefix);
2941                     SendToICS("refresh\n");
2942                 }
2943                 continue;
2944             }    
2945             
2946             /* Error messages */
2947 //          if (ics_user_moved) {
2948             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2949                 if (looking_at(buf, &i, "Illegal move") ||
2950                     looking_at(buf, &i, "Not a legal move") ||
2951                     looking_at(buf, &i, "Your king is in check") ||
2952                     looking_at(buf, &i, "It isn't your turn") ||
2953                     looking_at(buf, &i, "It is not your move")) {
2954                     /* Illegal move */
2955                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2956                         currentMove = --forwardMostMove;
2957                         DisplayMove(currentMove - 1); /* before DMError */
2958                         DrawPosition(FALSE, boards[currentMove]);
2959                         SwitchClocks();
2960                         DisplayBothClocks();
2961                     }
2962                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2963                     ics_user_moved = 0;
2964                     continue;
2965                 }
2966             }
2967
2968             if (looking_at(buf, &i, "still have time") ||
2969                 looking_at(buf, &i, "not out of time") ||
2970                 looking_at(buf, &i, "either player is out of time") ||
2971                 looking_at(buf, &i, "has timeseal; checking")) {
2972                 /* We must have called his flag a little too soon */
2973                 whiteFlag = blackFlag = FALSE;
2974                 continue;
2975             }
2976
2977             if (looking_at(buf, &i, "added * seconds to") ||
2978                 looking_at(buf, &i, "seconds were added to")) {
2979                 /* Update the clocks */
2980                 SendToICS(ics_prefix);
2981                 SendToICS("refresh\n");
2982                 continue;
2983             }
2984
2985             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2986                 ics_clock_paused = TRUE;
2987                 StopClocks();
2988                 continue;
2989             }
2990
2991             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2992                 ics_clock_paused = FALSE;
2993                 StartClocks();
2994                 continue;
2995             }
2996
2997             /* Grab player ratings from the Creating: message.
2998                Note we have to check for the special case when
2999                the ICS inserts things like [white] or [black]. */
3000             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3001                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3002                 /* star_matches:
3003                    0    player 1 name (not necessarily white)
3004                    1    player 1 rating
3005                    2    empty, white, or black (IGNORED)
3006                    3    player 2 name (not necessarily black)
3007                    4    player 2 rating
3008                    
3009                    The names/ratings are sorted out when the game
3010                    actually starts (below).
3011                 */
3012                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3013                 player1Rating = string_to_rating(star_match[1]);
3014                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3015                 player2Rating = string_to_rating(star_match[4]);
3016
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, 
3019                           "Ratings from 'Creating:' %s %d, %s %d\n",
3020                           player1Name, player1Rating,
3021                           player2Name, player2Rating);
3022
3023                 continue;
3024             }
3025             
3026             /* Improved generic start/end-of-game messages */
3027             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3028                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3029                 /* If tkind == 0: */
3030                 /* star_match[0] is the game number */
3031                 /*           [1] is the white player's name */
3032                 /*           [2] is the black player's name */
3033                 /* For end-of-game: */
3034                 /*           [3] is the reason for the game end */
3035                 /*           [4] is a PGN end game-token, preceded by " " */
3036                 /* For start-of-game: */
3037                 /*           [3] begins with "Creating" or "Continuing" */
3038                 /*           [4] is " *" or empty (don't care). */
3039                 int gamenum = atoi(star_match[0]);
3040                 char *whitename, *blackname, *why, *endtoken;
3041                 ChessMove endtype = (ChessMove) 0;
3042
3043                 if (tkind == 0) {
3044                   whitename = star_match[1];
3045                   blackname = star_match[2];
3046                   why = star_match[3];
3047                   endtoken = star_match[4];
3048                 } else {
3049                   whitename = star_match[1];
3050                   blackname = star_match[3];
3051                   why = star_match[5];
3052                   endtoken = star_match[6];
3053                 }
3054
3055                 /* Game start messages */
3056                 if (strncmp(why, "Creating ", 9) == 0 ||
3057                     strncmp(why, "Continuing ", 11) == 0) {
3058                     gs_gamenum = gamenum;
3059                     strcpy(gs_kind, strchr(why, ' ') + 1);
3060 #if ZIPPY
3061                     if (appData.zippyPlay) {
3062                         ZippyGameStart(whitename, blackname);
3063                     }
3064 #endif /*ZIPPY*/
3065                     continue;
3066                 }
3067
3068                 /* Game end messages */
3069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3070                     ics_gamenum != gamenum) {
3071                     continue;
3072                 }
3073                 while (endtoken[0] == ' ') endtoken++;
3074                 switch (endtoken[0]) {
3075                   case '*':
3076                   default:
3077                     endtype = GameUnfinished;
3078                     break;
3079                   case '0':
3080                     endtype = BlackWins;
3081                     break;
3082                   case '1':
3083                     if (endtoken[1] == '/')
3084                       endtype = GameIsDrawn;
3085                     else
3086                       endtype = WhiteWins;
3087                     break;
3088                 }
3089                 GameEnds(endtype, why, GE_ICS);
3090 #if ZIPPY
3091                 if (appData.zippyPlay && first.initDone) {
3092                     ZippyGameEnd(endtype, why);
3093                     if (first.pr == NULL) {
3094                       /* Start the next process early so that we'll
3095                          be ready for the next challenge */
3096                       StartChessProgram(&first);
3097                     }
3098                     /* Send "new" early, in case this command takes
3099                        a long time to finish, so that we'll be ready
3100                        for the next challenge. */
3101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3102                     Reset(TRUE, TRUE);
3103                 }
3104 #endif /*ZIPPY*/
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Removing game * from observation") ||
3109                 looking_at(buf, &i, "no longer observing game *") ||
3110                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3111                 if (gameMode == IcsObserving &&
3112                     atoi(star_match[0]) == ics_gamenum)
3113                   {
3114                       /* icsEngineAnalyze */
3115                       if (appData.icsEngineAnalyze) {
3116                             ExitAnalyzeMode();
3117                             ModeHighlight();
3118                       }
3119                       StopClocks();
3120                       gameMode = IcsIdle;
3121                       ics_gamenum = -1;
3122                       ics_user_moved = FALSE;
3123                   }
3124                 continue;
3125             }
3126
3127             if (looking_at(buf, &i, "no longer examining game *")) {
3128                 if (gameMode == IcsExamining &&
3129                     atoi(star_match[0]) == ics_gamenum)
3130                   {
3131                       gameMode = IcsIdle;
3132                       ics_gamenum = -1;
3133                       ics_user_moved = FALSE;
3134                   }
3135                 continue;
3136             }
3137
3138             /* Advance leftover_start past any newlines we find,
3139                so only partial lines can get reparsed */
3140             if (looking_at(buf, &i, "\n")) {
3141                 prevColor = curColor;
3142                 if (curColor != ColorNormal) {
3143                     if (oldi > next_out) {
3144                         SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = oldi;
3146                     }
3147                     Colorize(ColorNormal, FALSE);
3148                     curColor = ColorNormal;
3149                 }
3150                 if (started == STARTED_BOARD) {
3151                     started = STARTED_NONE;
3152                     parse[parse_pos] = NULLCHAR;
3153                     ParseBoard12(parse);
3154                     ics_user_moved = 0;
3155
3156                     /* Send premove here */
3157                     if (appData.premove) {
3158                       char str[MSG_SIZ];
3159                       if (currentMove == 0 &&
3160                           gameMode == IcsPlayingWhite &&
3161                           appData.premoveWhite) {
3162                         sprintf(str, "%s%s\n", ics_prefix,
3163                                 appData.premoveWhiteText);
3164                         if (appData.debugMode)
3165                           fprintf(debugFP, "Sending premove:\n");
3166                         SendToICS(str);
3167                       } else if (currentMove == 1 &&
3168                                  gameMode == IcsPlayingBlack &&
3169                                  appData.premoveBlack) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveBlackText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (gotPremove) {
3176                         gotPremove = 0;
3177                         ClearPremoveHighlights();
3178                         if (appData.debugMode)
3179                           fprintf(debugFP, "Sending premove:\n");
3180                           UserMoveEvent(premoveFromX, premoveFromY, 
3181                                         premoveToX, premoveToY, 
3182                                         premovePromoChar);
3183                       }
3184                     }
3185
3186                     /* Usually suppress following prompt */
3187                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3188                         if (looking_at(buf, &i, "*% ")) {
3189                             savingComment = FALSE;
3190                         }
3191                     }
3192                     next_out = i;
3193                 } else if (started == STARTED_HOLDINGS) {
3194                     int gamenum;
3195                     char new_piece[MSG_SIZ];
3196                     started = STARTED_NONE;
3197                     parse[parse_pos] = NULLCHAR;
3198                     if (appData.debugMode)
3199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3200                                                         parse, currentMove);
3201                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3202                         gamenum == ics_gamenum) {
3203                         if (gameInfo.variant == VariantNormal) {
3204                           /* [HGM] We seem to switch variant during a game!
3205                            * Presumably no holdings were displayed, so we have
3206                            * to move the position two files to the right to
3207                            * create room for them!
3208                            */
3209                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3210                           /* Get a move list just to see the header, which
3211                              will tell us whether this is really bug or zh */
3212                           if (ics_getting_history == H_FALSE) {
3213                             ics_getting_history = H_REQUESTED;
3214                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3215                             SendToICS(str);
3216                           }
3217                         }
3218                         new_piece[0] = NULLCHAR;
3219                         sscanf(parse, "game %d white [%s black [%s <- %s",
3220                                &gamenum, white_holding, black_holding,
3221                                new_piece);
3222                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3223                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3224                         /* [HGM] copy holdings to board holdings area */
3225                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3226                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3227 #if ZIPPY
3228                         if (appData.zippyPlay && first.initDone) {
3229                             ZippyHoldings(white_holding, black_holding,
3230                                           new_piece);
3231                         }
3232 #endif /*ZIPPY*/
3233                         if (tinyLayout || smallLayout) {
3234                             char wh[16], bh[16];
3235                             PackHolding(wh, white_holding);
3236                             PackHolding(bh, black_holding);
3237                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3238                                     gameInfo.white, gameInfo.black);
3239                         } else {
3240                             sprintf(str, "%s [%s] vs. %s [%s]",
3241                                     gameInfo.white, white_holding,
3242                                     gameInfo.black, black_holding);
3243                         }
3244
3245                         DrawPosition(FALSE, boards[currentMove]);
3246                         DisplayTitle(str);
3247                     }
3248                     /* Suppress following prompt */
3249                     if (looking_at(buf, &i, "*% ")) {
3250                         savingComment = FALSE;
3251                     }
3252                     next_out = i;
3253                 }
3254                 continue;
3255             }
3256
3257             i++;                /* skip unparsed character and loop back */
3258         }
3259         
3260         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3261             started != STARTED_HOLDINGS && i > next_out) {
3262             SendToPlayer(&buf[next_out], i - next_out);
3263             next_out = i;
3264         }
3265         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3266         
3267         leftover_len = buf_len - leftover_start;
3268         /* if buffer ends with something we couldn't parse,
3269            reparse it after appending the next read */
3270         
3271     } else if (count == 0) {
3272         RemoveInputSource(isr);
3273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3274     } else {
3275         DisplayFatalError(_("Error reading from ICS"), error, 1);
3276     }
3277 }
3278
3279
3280 /* Board style 12 looks like this:
3281    
3282    <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
3283    
3284  * The "<12> " is stripped before it gets to this routine.  The two
3285  * trailing 0's (flip state and clock ticking) are later addition, and
3286  * some chess servers may not have them, or may have only the first.
3287  * Additional trailing fields may be added in the future.  
3288  */
3289
3290 #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"
3291
3292 #define RELATION_OBSERVING_PLAYED    0
3293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3294 #define RELATION_PLAYING_MYMOVE      1
3295 #define RELATION_PLAYING_NOTMYMOVE  -1
3296 #define RELATION_EXAMINING           2
3297 #define RELATION_ISOLATED_BOARD     -3
3298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3299
3300 void
3301 ParseBoard12(string)
3302      char *string;
3303
3304     GameMode newGameMode;
3305     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3306     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3307     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3308     char to_play, board_chars[200];
3309     char move_str[500], str[500], elapsed_time[500];
3310     char black[32], white[32];
3311     Board board;
3312     int prevMove = currentMove;
3313     int ticking = 2;
3314     ChessMove moveType;
3315     int fromX, fromY, toX, toY;
3316     char promoChar;
3317     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3318     char *bookHit = NULL; // [HGM] book
3319
3320     fromX = fromY = toX = toY = -1;
3321     
3322     newGame = FALSE;
3323
3324     if (appData.debugMode)
3325       fprintf(debugFP, _("Parsing board: %s\n"), string);
3326
3327     move_str[0] = NULLCHAR;
3328     elapsed_time[0] = NULLCHAR;
3329     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3330         int  i = 0, j;
3331         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3332             if(string[i] == ' ') { ranks++; files = 0; }
3333             else files++;
3334             i++;
3335         }
3336         for(j = 0; j <i; j++) board_chars[j] = string[j];
3337         board_chars[i] = '\0';
3338         string += i + 1;
3339     }
3340     n = sscanf(string, PATTERN, &to_play, &double_push,
3341                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3342                &gamenum, white, black, &relation, &basetime, &increment,
3343                &white_stren, &black_stren, &white_time, &black_time,
3344                &moveNum, str, elapsed_time, move_str, &ics_flip,
3345                &ticking);
3346
3347     if (n < 21) {
3348         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3349         DisplayError(str, 0);
3350         return;
3351     }
3352
3353     /* Convert the move number to internal form */
3354     moveNum = (moveNum - 1) * 2;
3355     if (to_play == 'B') moveNum++;
3356     if (moveNum >= MAX_MOVES) {
3357       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3358                         0, 1);
3359       return;
3360     }
3361     
3362     switch (relation) {
3363       case RELATION_OBSERVING_PLAYED:
3364       case RELATION_OBSERVING_STATIC:
3365         if (gamenum == -1) {
3366             /* Old ICC buglet */
3367             relation = RELATION_OBSERVING_STATIC;
3368         }
3369         newGameMode = IcsObserving;
3370         break;
3371       case RELATION_PLAYING_MYMOVE:
3372       case RELATION_PLAYING_NOTMYMOVE:
3373         newGameMode =
3374           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3375             IcsPlayingWhite : IcsPlayingBlack;
3376         break;
3377       case RELATION_EXAMINING:
3378         newGameMode = IcsExamining;
3379         break;
3380       case RELATION_ISOLATED_BOARD:
3381       default:
3382         /* Just display this board.  If user was doing something else,
3383            we will forget about it until the next board comes. */ 
3384         newGameMode = IcsIdle;
3385         break;
3386       case RELATION_STARTING_POSITION:
3387         newGameMode = gameMode;
3388         break;
3389     }
3390     
3391     /* Modify behavior for initial board display on move listing
3392        of wild games.
3393        */
3394     switch (ics_getting_history) {
3395       case H_FALSE:
3396       case H_REQUESTED:
3397         break;
3398       case H_GOT_REQ_HEADER:
3399       case H_GOT_UNREQ_HEADER:
3400         /* This is the initial position of the current game */
3401         gamenum = ics_gamenum;
3402         moveNum = 0;            /* old ICS bug workaround */
3403         if (to_play == 'B') {
3404           startedFromSetupPosition = TRUE;
3405           blackPlaysFirst = TRUE;
3406           moveNum = 1;
3407           if (forwardMostMove == 0) forwardMostMove = 1;
3408           if (backwardMostMove == 0) backwardMostMove = 1;
3409           if (currentMove == 0) currentMove = 1;
3410         }
3411         newGameMode = gameMode;
3412         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3413         break;
3414       case H_GOT_UNWANTED_HEADER:
3415         /* This is an initial board that we don't want */
3416         return;
3417       case H_GETTING_MOVES:
3418         /* Should not happen */
3419         DisplayError(_("Error gathering move list: extra board"), 0);
3420         ics_getting_history = H_FALSE;
3421         return;
3422     }
3423     
3424     /* Take action if this is the first board of a new game, or of a
3425        different game than is currently being displayed.  */
3426     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3427         relation == RELATION_ISOLATED_BOARD) {
3428         
3429         /* Forget the old game and get the history (if any) of the new one */
3430         if (gameMode != BeginningOfGame) {
3431           Reset(FALSE, TRUE);
3432         }
3433         newGame = TRUE;
3434         if (appData.autoRaiseBoard) BoardToTop();
3435         prevMove = -3;
3436         if (gamenum == -1) {
3437             newGameMode = IcsIdle;
3438         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3439                    appData.getMoveList) {
3440             /* Need to get game history */
3441             ics_getting_history = H_REQUESTED;
3442             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3443             SendToICS(str);
3444         }
3445         
3446         /* Initially flip the board to have black on the bottom if playing
3447            black or if the ICS flip flag is set, but let the user change
3448            it with the Flip View button. */
3449         flipView = appData.autoFlipView ? 
3450           (newGameMode == IcsPlayingBlack) || ics_flip :
3451           appData.flipView;
3452         
3453         /* Done with values from previous mode; copy in new ones */
3454         gameMode = newGameMode;
3455         ModeHighlight();
3456         ics_gamenum = gamenum;
3457         if (gamenum == gs_gamenum) {
3458             int klen = strlen(gs_kind);
3459             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3460             sprintf(str, "ICS %s", gs_kind);
3461             gameInfo.event = StrSave(str);
3462         } else {
3463             gameInfo.event = StrSave("ICS game");
3464         }
3465         gameInfo.site = StrSave(appData.icsHost);
3466         gameInfo.date = PGNDate();
3467         gameInfo.round = StrSave("-");
3468         gameInfo.white = StrSave(white);
3469         gameInfo.black = StrSave(black);
3470         timeControl = basetime * 60 * 1000;
3471         timeControl_2 = 0;
3472         timeIncrement = increment * 1000;
3473         movesPerSession = 0;
3474         gameInfo.timeControl = TimeControlTagValue();
3475         VariantSwitch(board, StringToVariant(gameInfo.event) );
3476   if (appData.debugMode) {
3477     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3478     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3479     setbuf(debugFP, NULL);
3480   }
3481
3482         gameInfo.outOfBook = NULL;
3483         
3484         /* Do we have the ratings? */
3485         if (strcmp(player1Name, white) == 0 &&
3486             strcmp(player2Name, black) == 0) {
3487             if (appData.debugMode)
3488               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3489                       player1Rating, player2Rating);
3490             gameInfo.whiteRating = player1Rating;
3491             gameInfo.blackRating = player2Rating;
3492         } else if (strcmp(player2Name, white) == 0 &&
3493                    strcmp(player1Name, black) == 0) {
3494             if (appData.debugMode)
3495               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3496                       player2Rating, player1Rating);
3497             gameInfo.whiteRating = player2Rating;
3498             gameInfo.blackRating = player1Rating;
3499         }
3500         player1Name[0] = player2Name[0] = NULLCHAR;
3501
3502         /* Silence shouts if requested */
3503         if (appData.quietPlay &&
3504             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3505             SendToICS(ics_prefix);
3506             SendToICS("set shout 0\n");
3507         }
3508     }
3509     
3510     /* Deal with midgame name changes */
3511     if (!newGame) {
3512         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3513             if (gameInfo.white) free(gameInfo.white);
3514             gameInfo.white = StrSave(white);
3515         }
3516         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3517             if (gameInfo.black) free(gameInfo.black);
3518             gameInfo.black = StrSave(black);
3519         }
3520     }
3521     
3522     /* Throw away game result if anything actually changes in examine mode */
3523     if (gameMode == IcsExamining && !newGame) {
3524         gameInfo.result = GameUnfinished;
3525         if (gameInfo.resultDetails != NULL) {
3526             free(gameInfo.resultDetails);
3527             gameInfo.resultDetails = NULL;
3528         }
3529     }
3530     
3531     /* In pausing && IcsExamining mode, we ignore boards coming
3532        in if they are in a different variation than we are. */
3533     if (pauseExamInvalid) return;
3534     if (pausing && gameMode == IcsExamining) {
3535         if (moveNum <= pauseExamForwardMostMove) {
3536             pauseExamInvalid = TRUE;
3537             forwardMostMove = pauseExamForwardMostMove;
3538             return;
3539         }
3540     }
3541     
3542   if (appData.debugMode) {
3543     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3544   }
3545     /* Parse the board */
3546     for (k = 0; k < ranks; k++) {
3547       for (j = 0; j < files; j++)
3548         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3549       if(gameInfo.holdingsWidth > 1) {
3550            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3551            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3552       }
3553     }
3554     CopyBoard(boards[moveNum], board);
3555     if (moveNum == 0) {
3556         startedFromSetupPosition =
3557           !CompareBoards(board, initialPosition);
3558         if(startedFromSetupPosition)
3559             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3560     }
3561
3562     /* [HGM] Set castling rights. Take the outermost Rooks,
3563        to make it also work for FRC opening positions. Note that board12
3564        is really defective for later FRC positions, as it has no way to
3565        indicate which Rook can castle if they are on the same side of King.
3566        For the initial position we grant rights to the outermost Rooks,
3567        and remember thos rights, and we then copy them on positions
3568        later in an FRC game. This means WB might not recognize castlings with
3569        Rooks that have moved back to their original position as illegal,
3570        but in ICS mode that is not its job anyway.
3571     */
3572     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3573     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3574
3575         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3576             if(board[0][i] == WhiteRook) j = i;
3577         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3578         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3579             if(board[0][i] == WhiteRook) j = i;
3580         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3581         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3583         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3584         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3585             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3586         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587
3588         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3590             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3592             if(board[BOARD_HEIGHT-1][k] == bKing)
3593                 initialRights[5] = castlingRights[moveNum][5] = k;
3594     } else { int r;
3595         r = castlingRights[moveNum][0] = initialRights[0];
3596         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3597         r = castlingRights[moveNum][1] = initialRights[1];
3598         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3599         r = castlingRights[moveNum][3] = initialRights[3];
3600         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3601         r = castlingRights[moveNum][4] = initialRights[4];
3602         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3603         /* wildcastle kludge: always assume King has rights */
3604         r = castlingRights[moveNum][2] = initialRights[2];
3605         r = castlingRights[moveNum][5] = initialRights[5];
3606     }
3607     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3608     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3609
3610     
3611     if (ics_getting_history == H_GOT_REQ_HEADER ||
3612         ics_getting_history == H_GOT_UNREQ_HEADER) {
3613         /* This was an initial position from a move list, not
3614            the current position */
3615         return;
3616     }
3617     
3618     /* Update currentMove and known move number limits */
3619     newMove = newGame || moveNum > forwardMostMove;
3620
3621     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3622     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3623         takeback = forwardMostMove - moveNum;
3624         for (i = 0; i < takeback; i++) {
3625              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3626              SendToProgram("undo\n", &first);
3627         }
3628     }
3629
3630     if (newGame) {
3631         forwardMostMove = backwardMostMove = currentMove = moveNum;
3632         if (gameMode == IcsExamining && moveNum == 0) {
3633           /* Workaround for ICS limitation: we are not told the wild
3634              type when starting to examine a game.  But if we ask for
3635              the move list, the move list header will tell us */
3636             ics_getting_history = H_REQUESTED;
3637             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3638             SendToICS(str);
3639         }
3640     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3641                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3642         forwardMostMove = moveNum;
3643         if (!pausing || currentMove > forwardMostMove)
3644           currentMove = forwardMostMove;
3645     } else {
3646         /* New part of history that is not contiguous with old part */ 
3647         if (pausing && gameMode == IcsExamining) {
3648             pauseExamInvalid = TRUE;
3649             forwardMostMove = pauseExamForwardMostMove;
3650             return;
3651         }
3652         forwardMostMove = backwardMostMove = currentMove = moveNum;
3653         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3654             ics_getting_history = H_REQUESTED;
3655             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656             SendToICS(str);
3657         }
3658     }
3659     
3660     /* Update the clocks */
3661     if (strchr(elapsed_time, '.')) {
3662       /* Time is in ms */
3663       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3664       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3665     } else {
3666       /* Time is in seconds */
3667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3669     }
3670       
3671
3672 #if ZIPPY
3673     if (appData.zippyPlay && newGame &&
3674         gameMode != IcsObserving && gameMode != IcsIdle &&
3675         gameMode != IcsExamining)
3676       ZippyFirstBoard(moveNum, basetime, increment);
3677 #endif
3678     
3679     /* Put the move on the move list, first converting
3680        to canonical algebraic form. */
3681     if (moveNum > 0) {
3682   if (appData.debugMode) {
3683     if (appData.debugMode) { int f = forwardMostMove;
3684         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3685                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3686     }
3687     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3688     fprintf(debugFP, "moveNum = %d\n", moveNum);
3689     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3690     setbuf(debugFP, NULL);
3691   }
3692         if (moveNum <= backwardMostMove) {
3693             /* We don't know what the board looked like before
3694                this move.  Punt. */
3695             strcpy(parseList[moveNum - 1], move_str);
3696             strcat(parseList[moveNum - 1], " ");
3697             strcat(parseList[moveNum - 1], elapsed_time);
3698             moveList[moveNum - 1][0] = NULLCHAR;
3699         } else if (strcmp(move_str, "none") == 0) {
3700             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3701             /* Again, we don't know what the board looked like;
3702                this is really the start of the game. */
3703             parseList[moveNum - 1][0] = NULLCHAR;
3704             moveList[moveNum - 1][0] = NULLCHAR;
3705             backwardMostMove = moveNum;
3706             startedFromSetupPosition = TRUE;
3707             fromX = fromY = toX = toY = -1;
3708         } else {
3709           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3710           //                 So we parse the long-algebraic move string in stead of the SAN move
3711           int valid; char buf[MSG_SIZ], *prom;
3712
3713           // str looks something like "Q/a1-a2"; kill the slash
3714           if(str[1] == '/') 
3715                 sprintf(buf, "%c%s", str[0], str+2);
3716           else  strcpy(buf, str); // might be castling
3717           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3718                 strcat(buf, prom); // long move lacks promo specification!
3719           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3720                 if(appData.debugMode) 
3721                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3722                 strcpy(move_str, buf);
3723           }
3724           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3725                                 &fromX, &fromY, &toX, &toY, &promoChar)
3726                || ParseOneMove(buf, moveNum - 1, &moveType,
3727                                 &fromX, &fromY, &toX, &toY, &promoChar);
3728           // end of long SAN patch
3729           if (valid) {
3730             (void) CoordsToAlgebraic(boards[moveNum - 1],
3731                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3732                                      fromY, fromX, toY, toX, promoChar,
3733                                      parseList[moveNum-1]);
3734             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3735                              castlingRights[moveNum]) ) {
3736               case MT_NONE:
3737               case MT_STALEMATE:
3738               default:
3739                 break;
3740               case MT_CHECK:
3741                 if(gameInfo.variant != VariantShogi)
3742                     strcat(parseList[moveNum - 1], "+");
3743                 break;
3744               case MT_CHECKMATE:
3745               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3746                 strcat(parseList[moveNum - 1], "#");
3747                 break;
3748             }
3749             strcat(parseList[moveNum - 1], " ");
3750             strcat(parseList[moveNum - 1], elapsed_time);
3751             /* currentMoveString is set as a side-effect of ParseOneMove */
3752             strcpy(moveList[moveNum - 1], currentMoveString);
3753             strcat(moveList[moveNum - 1], "\n");
3754           } else {
3755             /* Move from ICS was illegal!?  Punt. */
3756   if (appData.debugMode) {
3757     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3758     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3759   }
3760             strcpy(parseList[moveNum - 1], move_str);
3761             strcat(parseList[moveNum - 1], " ");
3762             strcat(parseList[moveNum - 1], elapsed_time);
3763             moveList[moveNum - 1][0] = NULLCHAR;
3764             fromX = fromY = toX = toY = -1;
3765           }
3766         }
3767   if (appData.debugMode) {
3768     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3769     setbuf(debugFP, NULL);
3770   }
3771
3772 #if ZIPPY
3773         /* Send move to chess program (BEFORE animating it). */
3774         if (appData.zippyPlay && !newGame && newMove && 
3775            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3776
3777             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3778                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3779                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3780                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3781                             move_str);
3782                     DisplayError(str, 0);
3783                 } else {
3784                     if (first.sendTime) {
3785                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3786                     }
3787                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3788                     if (firstMove && !bookHit) {
3789                         firstMove = FALSE;
3790                         if (first.useColors) {
3791                           SendToProgram(gameMode == IcsPlayingWhite ?
3792                                         "white\ngo\n" :
3793                                         "black\ngo\n", &first);
3794                         } else {
3795                           SendToProgram("go\n", &first);
3796                         }
3797                         first.maybeThinking = TRUE;
3798                     }
3799                 }
3800             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3801               if (moveList[moveNum - 1][0] == NULLCHAR) {
3802                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3803                 DisplayError(str, 0);
3804               } else {
3805                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3806                 SendMoveToProgram(moveNum - 1, &first);
3807               }
3808             }
3809         }
3810 #endif
3811     }
3812
3813     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3814         /* If move comes from a remote source, animate it.  If it
3815            isn't remote, it will have already been animated. */
3816         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3817             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3818         }
3819         if (!pausing && appData.highlightLastMove) {
3820             SetHighlights(fromX, fromY, toX, toY);
3821         }
3822     }
3823     
3824     /* Start the clocks */
3825     whiteFlag = blackFlag = FALSE;
3826     appData.clockMode = !(basetime == 0 && increment == 0);
3827     if (ticking == 0) {
3828       ics_clock_paused = TRUE;
3829       StopClocks();
3830     } else if (ticking == 1) {
3831       ics_clock_paused = FALSE;
3832     }
3833     if (gameMode == IcsIdle ||
3834         relation == RELATION_OBSERVING_STATIC ||
3835         relation == RELATION_EXAMINING ||
3836         ics_clock_paused)
3837       DisplayBothClocks();
3838     else
3839       StartClocks();
3840     
3841     /* Display opponents and material strengths */
3842     if (gameInfo.variant != VariantBughouse &&
3843         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3844         if (tinyLayout || smallLayout) {
3845             if(gameInfo.variant == VariantNormal)
3846                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3847                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3848                     basetime, increment);
3849             else
3850                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3851                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3852                     basetime, increment, (int) gameInfo.variant);
3853         } else {
3854             if(gameInfo.variant == VariantNormal)
3855                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3856                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3857                     basetime, increment);
3858             else
3859                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3861                     basetime, increment, VariantName(gameInfo.variant));
3862         }
3863         DisplayTitle(str);
3864   if (appData.debugMode) {
3865     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3866   }
3867     }
3868
3869    
3870     /* Display the board */
3871     if (!pausing && !appData.noGUI) {
3872       
3873       if (appData.premove)
3874           if (!gotPremove || 
3875              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3876              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3877               ClearPremoveHighlights();
3878
3879       DrawPosition(FALSE, boards[currentMove]);
3880       DisplayMove(moveNum - 1);
3881       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3882             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3883               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3884         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3885       }
3886     }
3887
3888     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3889 #if ZIPPY
3890     if(bookHit) { // [HGM] book: simulate book reply
3891         static char bookMove[MSG_SIZ]; // a bit generous?
3892
3893         programStats.nodes = programStats.depth = programStats.time = 
3894         programStats.score = programStats.got_only_move = 0;
3895         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3896
3897         strcpy(bookMove, "move ");
3898         strcat(bookMove, bookHit);
3899         HandleMachineMove(bookMove, &first);
3900     }
3901 #endif
3902 }
3903
3904 void
3905 GetMoveListEvent()
3906 {
3907     char buf[MSG_SIZ];
3908     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3909         ics_getting_history = H_REQUESTED;
3910         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3911         SendToICS(buf);
3912     }
3913 }
3914
3915 void
3916 AnalysisPeriodicEvent(force)
3917      int force;
3918 {
3919     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3920          && !force) || !appData.periodicUpdates)
3921       return;
3922
3923     /* Send . command to Crafty to collect stats */
3924     SendToProgram(".\n", &first);
3925
3926     /* Don't send another until we get a response (this makes
3927        us stop sending to old Crafty's which don't understand
3928        the "." command (sending illegal cmds resets node count & time,
3929        which looks bad)) */
3930     programStats.ok_to_send = 0;
3931 }
3932
3933 void
3934 SendMoveToProgram(moveNum, cps)
3935      int moveNum;
3936      ChessProgramState *cps;
3937 {
3938     char buf[MSG_SIZ];
3939
3940     if (cps->useUsermove) {
3941       SendToProgram("usermove ", cps);
3942     }
3943     if (cps->useSAN) {
3944       char *space;
3945       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3946         int len = space - parseList[moveNum];
3947         memcpy(buf, parseList[moveNum], len);
3948         buf[len++] = '\n';
3949         buf[len] = NULLCHAR;
3950       } else {
3951         sprintf(buf, "%s\n", parseList[moveNum]);
3952       }
3953       SendToProgram(buf, cps);
3954     } else {
3955       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3956         AlphaRank(moveList[moveNum], 4);
3957         SendToProgram(moveList[moveNum], cps);
3958         AlphaRank(moveList[moveNum], 4); // and back
3959       } else
3960       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3961        * the engine. It would be nice to have a better way to identify castle 
3962        * moves here. */
3963       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3964                                                                          && cps->useOOCastle) {
3965         int fromX = moveList[moveNum][0] - AAA; 
3966         int fromY = moveList[moveNum][1] - ONE;
3967         int toX = moveList[moveNum][2] - AAA; 
3968         int toY = moveList[moveNum][3] - ONE;
3969         if((boards[moveNum][fromY][fromX] == WhiteKing 
3970             && boards[moveNum][toY][toX] == WhiteRook)
3971            || (boards[moveNum][fromY][fromX] == BlackKing 
3972                && boards[moveNum][toY][toX] == BlackRook)) {
3973           if(toX > fromX) SendToProgram("O-O\n", cps);
3974           else SendToProgram("O-O-O\n", cps);
3975         }
3976         else SendToProgram(moveList[moveNum], cps);
3977       }
3978       else SendToProgram(moveList[moveNum], cps);
3979       /* End of additions by Tord */
3980     }
3981
3982     /* [HGM] setting up the opening has brought engine in force mode! */
3983     /*       Send 'go' if we are in a mode where machine should play. */
3984     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3985         (gameMode == TwoMachinesPlay   ||
3986 #ifdef ZIPPY
3987          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
3988 #endif
3989          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
3990         SendToProgram("go\n", cps);
3991   if (appData.debugMode) {
3992     fprintf(debugFP, "(extra)\n");
3993   }
3994     }
3995     setboardSpoiledMachineBlack = 0;
3996 }
3997
3998 void
3999 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4000      ChessMove moveType;
4001      int fromX, fromY, toX, toY;
4002 {
4003     char user_move[MSG_SIZ];
4004
4005     switch (moveType) {
4006       default:
4007         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4008                 (int)moveType, fromX, fromY, toX, toY);
4009         DisplayError(user_move + strlen("say "), 0);
4010         break;
4011       case WhiteKingSideCastle:
4012       case BlackKingSideCastle:
4013       case WhiteQueenSideCastleWild:
4014       case BlackQueenSideCastleWild:
4015       /* PUSH Fabien */
4016       case WhiteHSideCastleFR:
4017       case BlackHSideCastleFR:
4018       /* POP Fabien */
4019         sprintf(user_move, "o-o\n");
4020         break;
4021       case WhiteQueenSideCastle:
4022       case BlackQueenSideCastle:
4023       case WhiteKingSideCastleWild:
4024       case BlackKingSideCastleWild:
4025       /* PUSH Fabien */
4026       case WhiteASideCastleFR:
4027       case BlackASideCastleFR:
4028       /* POP Fabien */
4029         sprintf(user_move, "o-o-o\n");
4030         break;
4031       case WhitePromotionQueen:
4032       case BlackPromotionQueen:
4033       case WhitePromotionRook:
4034       case BlackPromotionRook:
4035       case WhitePromotionBishop:
4036       case BlackPromotionBishop:
4037       case WhitePromotionKnight:
4038       case BlackPromotionKnight:
4039       case WhitePromotionKing:
4040       case BlackPromotionKing:
4041       case WhitePromotionChancellor:
4042       case BlackPromotionChancellor:
4043       case WhitePromotionArchbishop:
4044       case BlackPromotionArchbishop:
4045         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4046             sprintf(user_move, "%c%c%c%c=%c\n",
4047                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4048                 PieceToChar(WhiteFerz));
4049         else if(gameInfo.variant == VariantGreat)
4050             sprintf(user_move, "%c%c%c%c=%c\n",
4051                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4052                 PieceToChar(WhiteMan));
4053         else
4054             sprintf(user_move, "%c%c%c%c=%c\n",
4055                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4056                 PieceToChar(PromoPiece(moveType)));
4057         break;
4058       case WhiteDrop:
4059       case BlackDrop:
4060         sprintf(user_move, "%c@%c%c\n",
4061                 ToUpper(PieceToChar((ChessSquare) fromX)),
4062                 AAA + toX, ONE + toY);
4063         break;
4064       case NormalMove:
4065       case WhiteCapturesEnPassant:
4066       case BlackCapturesEnPassant:
4067       case IllegalMove:  /* could be a variant we don't quite understand */
4068         sprintf(user_move, "%c%c%c%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4070         break;
4071     }
4072     SendToICS(user_move);
4073     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4074         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4075 }
4076
4077 void
4078 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4079      int rf, ff, rt, ft;
4080      char promoChar;
4081      char move[7];
4082 {
4083     if (rf == DROP_RANK) {
4084         sprintf(move, "%c@%c%c\n",
4085                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4086     } else {
4087         if (promoChar == 'x' || promoChar == NULLCHAR) {
4088             sprintf(move, "%c%c%c%c\n",
4089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4090         } else {
4091             sprintf(move, "%c%c%c%c%c\n",
4092                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4093         }
4094     }
4095 }
4096
4097 void
4098 ProcessICSInitScript(f)
4099      FILE *f;
4100 {
4101     char buf[MSG_SIZ];
4102
4103     while (fgets(buf, MSG_SIZ, f)) {
4104         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4105     }
4106
4107     fclose(f);
4108 }
4109
4110
4111 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4112 void
4113 AlphaRank(char *move, int n)
4114 {
4115 //    char *p = move, c; int x, y;
4116
4117     if (appData.debugMode) {
4118         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4119     }
4120
4121     if(move[1]=='*' && 
4122        move[2]>='0' && move[2]<='9' &&
4123        move[3]>='a' && move[3]<='x'    ) {
4124         move[1] = '@';
4125         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4126         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4127     } else
4128     if(move[0]>='0' && move[0]<='9' &&
4129        move[1]>='a' && move[1]<='x' &&
4130        move[2]>='0' && move[2]<='9' &&
4131        move[3]>='a' && move[3]<='x'    ) {
4132         /* input move, Shogi -> normal */
4133         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4134         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4135         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4136         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4137     } else
4138     if(move[1]=='@' &&
4139        move[3]>='0' && move[3]<='9' &&
4140        move[2]>='a' && move[2]<='x'    ) {
4141         move[1] = '*';
4142         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4143         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4144     } else
4145     if(
4146        move[0]>='a' && move[0]<='x' &&
4147        move[3]>='0' && move[3]<='9' &&
4148        move[2]>='a' && move[2]<='x'    ) {
4149          /* output move, normal -> Shogi */
4150         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4151         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4152         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4153         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4154         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4155     }
4156     if (appData.debugMode) {
4157         fprintf(debugFP, "   out = '%s'\n", move);
4158     }
4159 }
4160
4161 /* Parser for moves from gnuchess, ICS, or user typein box */
4162 Boolean
4163 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4164      char *move;
4165      int moveNum;
4166      ChessMove *moveType;
4167      int *fromX, *fromY, *toX, *toY;
4168      char *promoChar;
4169 {       
4170     if (appData.debugMode) {
4171         fprintf(debugFP, "move to parse: %s\n", move);
4172     }
4173     *moveType = yylexstr(moveNum, move);
4174
4175     switch (*moveType) {
4176       case WhitePromotionChancellor:
4177       case BlackPromotionChancellor:
4178       case WhitePromotionArchbishop:
4179       case BlackPromotionArchbishop:
4180       case WhitePromotionQueen:
4181       case BlackPromotionQueen:
4182       case WhitePromotionRook:
4183       case BlackPromotionRook:
4184       case WhitePromotionBishop:
4185       case BlackPromotionBishop:
4186       case WhitePromotionKnight:
4187       case BlackPromotionKnight:
4188       case WhitePromotionKing:
4189       case BlackPromotionKing:
4190       case NormalMove:
4191       case WhiteCapturesEnPassant:
4192       case BlackCapturesEnPassant:
4193       case WhiteKingSideCastle:
4194       case WhiteQueenSideCastle:
4195       case BlackKingSideCastle:
4196       case BlackQueenSideCastle:
4197       case WhiteKingSideCastleWild:
4198       case WhiteQueenSideCastleWild:
4199       case BlackKingSideCastleWild:
4200       case BlackQueenSideCastleWild:
4201       /* Code added by Tord: */
4202       case WhiteHSideCastleFR:
4203       case WhiteASideCastleFR:
4204       case BlackHSideCastleFR:
4205       case BlackASideCastleFR:
4206       /* End of code added by Tord */
4207       case IllegalMove:         /* bug or odd chess variant */
4208         *fromX = currentMoveString[0] - AAA;
4209         *fromY = currentMoveString[1] - ONE;
4210         *toX = currentMoveString[2] - AAA;
4211         *toY = currentMoveString[3] - ONE;
4212         *promoChar = currentMoveString[4];
4213         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4214             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4215     if (appData.debugMode) {
4216         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4217     }
4218             *fromX = *fromY = *toX = *toY = 0;
4219             return FALSE;
4220         }
4221         if (appData.testLegality) {
4222           return (*moveType != IllegalMove);
4223         } else {
4224           return !(fromX == fromY && toX == toY);
4225         }
4226
4227       case WhiteDrop:
4228       case BlackDrop:
4229         *fromX = *moveType == WhiteDrop ?
4230           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4231           (int) CharToPiece(ToLower(currentMoveString[0]));
4232         *fromY = DROP_RANK;
4233         *toX = currentMoveString[2] - AAA;
4234         *toY = currentMoveString[3] - ONE;
4235         *promoChar = NULLCHAR;
4236         return TRUE;
4237
4238       case AmbiguousMove:
4239       case ImpossibleMove:
4240       case (ChessMove) 0:       /* end of file */
4241       case ElapsedTime:
4242       case Comment:
4243       case PGNTag:
4244       case NAG:
4245       case WhiteWins:
4246       case BlackWins:
4247       case GameIsDrawn:
4248       default:
4249     if (appData.debugMode) {
4250         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4251     }
4252         /* bug? */
4253         *fromX = *fromY = *toX = *toY = 0;
4254         *promoChar = NULLCHAR;
4255         return FALSE;
4256     }
4257 }
4258
4259 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4260 // All positions will have equal probability, but the current method will not provide a unique
4261 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4262 #define DARK 1
4263 #define LITE 2
4264 #define ANY 3
4265
4266 int squaresLeft[4];
4267 int piecesLeft[(int)BlackPawn];
4268 int seed, nrOfShuffles;
4269
4270 void GetPositionNumber()
4271 {       // sets global variable seed
4272         int i;
4273
4274         seed = appData.defaultFrcPosition;
4275         if(seed < 0) { // randomize based on time for negative FRC position numbers
4276                 for(i=0; i<50; i++) seed += random();
4277                 seed = random() ^ random() >> 8 ^ random() << 8;
4278                 if(seed<0) seed = -seed;
4279         }
4280 }
4281
4282 int put(Board board, int pieceType, int rank, int n, int shade)
4283 // put the piece on the (n-1)-th empty squares of the given shade
4284 {
4285         int i;
4286
4287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4288                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4289                         board[rank][i] = (ChessSquare) pieceType;
4290                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4291                         squaresLeft[ANY]--;
4292                         piecesLeft[pieceType]--; 
4293                         return i;
4294                 }
4295         }
4296         return -1;
4297 }
4298
4299
4300 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4301 // calculate where the next piece goes, (any empty square), and put it there
4302 {
4303         int i;
4304
4305         i = seed % squaresLeft[shade];
4306         nrOfShuffles *= squaresLeft[shade];
4307         seed /= squaresLeft[shade];
4308         put(board, pieceType, rank, i, shade);
4309 }
4310
4311 void AddTwoPieces(Board board, int pieceType, int rank)
4312 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4313 {
4314         int i, n=squaresLeft[ANY], j=n-1, k;
4315
4316         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4317         i = seed % k;  // pick one
4318         nrOfShuffles *= k;
4319         seed /= k;
4320         while(i >= j) i -= j--;
4321         j = n - 1 - j; i += j;
4322         put(board, pieceType, rank, j, ANY);
4323         put(board, pieceType, rank, i, ANY);
4324 }
4325
4326 void SetUpShuffle(Board board, int number)
4327 {
4328         int i, p, first=1;
4329
4330         GetPositionNumber(); nrOfShuffles = 1;
4331
4332         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4333         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4334         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4335
4336         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4337
4338         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4339             p = (int) board[0][i];
4340             if(p < (int) BlackPawn) piecesLeft[p] ++;
4341             board[0][i] = EmptySquare;
4342         }
4343
4344         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4345             // shuffles restricted to allow normal castling put KRR first
4346             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4347                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4348             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4349                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4350             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4351                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4352             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4353                 put(board, WhiteRook, 0, 0, ANY);
4354             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4355         }
4356
4357         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4358             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4359             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4360                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4361                 while(piecesLeft[p] >= 2) {
4362                     AddOnePiece(board, p, 0, LITE);
4363                     AddOnePiece(board, p, 0, DARK);
4364                 }
4365                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4366             }
4367
4368         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4369             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4370             // but we leave King and Rooks for last, to possibly obey FRC restriction
4371             if(p == (int)WhiteRook) continue;
4372             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4373             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4374         }
4375
4376         // now everything is placed, except perhaps King (Unicorn) and Rooks
4377
4378         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4379             // Last King gets castling rights
4380             while(piecesLeft[(int)WhiteUnicorn]) {
4381                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4382                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4383             }
4384
4385             while(piecesLeft[(int)WhiteKing]) {
4386                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4387                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4388             }
4389
4390
4391         } else {
4392             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4393             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4394         }
4395
4396         // Only Rooks can be left; simply place them all
4397         while(piecesLeft[(int)WhiteRook]) {
4398                 i = put(board, WhiteRook, 0, 0, ANY);
4399                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4400                         if(first) {
4401                                 first=0;
4402                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4403                         }
4404                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4405                 }
4406         }
4407         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4408             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4409         }
4410
4411         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4412 }
4413
4414 int SetCharTable( char *table, const char * map )
4415 /* [HGM] moved here from winboard.c because of its general usefulness */
4416 /*       Basically a safe strcpy that uses the last character as King */
4417 {
4418     int result = FALSE; int NrPieces;
4419
4420     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4421                     && NrPieces >= 12 && !(NrPieces&1)) {
4422         int i; /* [HGM] Accept even length from 12 to 34 */
4423
4424         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4425         for( i=0; i<NrPieces/2-1; i++ ) {
4426             table[i] = map[i];
4427             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4428         }
4429         table[(int) WhiteKing]  = map[NrPieces/2-1];
4430         table[(int) BlackKing]  = map[NrPieces-1];
4431
4432         result = TRUE;
4433     }
4434
4435     return result;
4436 }
4437
4438 void Prelude(Board board)
4439 {       // [HGM] superchess: random selection of exo-pieces
4440         int i, j, k; ChessSquare p; 
4441         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4442
4443         GetPositionNumber(); // use FRC position number
4444
4445         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4446             SetCharTable(pieceToChar, appData.pieceToCharTable);
4447             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4448                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4449         }
4450
4451         j = seed%4;                 seed /= 4; 
4452         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4453         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4454         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4455         j = seed%3 + (seed%3 >= j); seed /= 3; 
4456         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4457         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4458         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4459         j = seed%3;                 seed /= 3; 
4460         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4461         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4462         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4463         j = seed%2 + (seed%2 >= j); seed /= 2; 
4464         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4467         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4468         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4469         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4470         put(board, exoPieces[0],    0, 0, ANY);
4471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4472 }
4473
4474 void
4475 InitPosition(redraw)
4476      int redraw;
4477 {
4478     ChessSquare (* pieces)[BOARD_SIZE];
4479     int i, j, pawnRow, overrule,
4480     oldx = gameInfo.boardWidth,
4481     oldy = gameInfo.boardHeight,
4482     oldh = gameInfo.holdingsWidth,
4483     oldv = gameInfo.variant;
4484
4485     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4486
4487     /* [AS] Initialize pv info list [HGM] and game status */
4488     {
4489         for( i=0; i<MAX_MOVES; i++ ) {
4490             pvInfoList[i].depth = 0;
4491             epStatus[i]=EP_NONE;
4492             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4493         }
4494
4495         initialRulePlies = 0; /* 50-move counter start */
4496
4497         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4498         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4499     }
4500
4501     
4502     /* [HGM] logic here is completely changed. In stead of full positions */
4503     /* the initialized data only consist of the two backranks. The switch */
4504     /* selects which one we will use, which is than copied to the Board   */
4505     /* initialPosition, which for the rest is initialized by Pawns and    */
4506     /* empty squares. This initial position is then copied to boards[0],  */
4507     /* possibly after shuffling, so that it remains available.            */
4508
4509     gameInfo.holdingsWidth = 0; /* default board sizes */
4510     gameInfo.boardWidth    = 8;
4511     gameInfo.boardHeight   = 8;
4512     gameInfo.holdingsSize  = 0;
4513     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4514     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4515     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4516
4517     switch (gameInfo.variant) {
4518     case VariantFischeRandom:
4519       shuffleOpenings = TRUE;
4520     default:
4521       pieces = FIDEArray;
4522       break;
4523     case VariantShatranj:
4524       pieces = ShatranjArray;
4525       nrCastlingRights = 0;
4526       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4527       break;
4528     case VariantTwoKings:
4529       pieces = twoKingsArray;
4530       break;
4531     case VariantCapaRandom:
4532       shuffleOpenings = TRUE;
4533     case VariantCapablanca:
4534       pieces = CapablancaArray;
4535       gameInfo.boardWidth = 10;
4536       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4537       break;
4538     case VariantGothic:
4539       pieces = GothicArray;
4540       gameInfo.boardWidth = 10;
4541       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4542       break;
4543     case VariantJanus:
4544       pieces = JanusArray;
4545       gameInfo.boardWidth = 10;
4546       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4547       nrCastlingRights = 6;
4548         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4549         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4550         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4551         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4552         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4553         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4554       break;
4555     case VariantFalcon:
4556       pieces = FalconArray;
4557       gameInfo.boardWidth = 10;
4558       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4559       break;
4560     case VariantXiangqi:
4561       pieces = XiangqiArray;
4562       gameInfo.boardWidth  = 9;
4563       gameInfo.boardHeight = 10;
4564       nrCastlingRights = 0;
4565       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4566       break;
4567     case VariantShogi:
4568       pieces = ShogiArray;
4569       gameInfo.boardWidth  = 9;
4570       gameInfo.boardHeight = 9;
4571       gameInfo.holdingsSize = 7;
4572       nrCastlingRights = 0;
4573       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4574       break;
4575     case VariantCourier:
4576       pieces = CourierArray;
4577       gameInfo.boardWidth  = 12;
4578       nrCastlingRights = 0;
4579       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4580       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4581       break;
4582     case VariantKnightmate:
4583       pieces = KnightmateArray;
4584       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4585       break;
4586     case VariantFairy:
4587       pieces = fairyArray;
4588       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4589       break;
4590     case VariantGreat:
4591       pieces = GreatArray;
4592       gameInfo.boardWidth = 10;
4593       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4594       gameInfo.holdingsSize = 8;
4595       break;
4596     case VariantSuper:
4597       pieces = FIDEArray;
4598       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4599       gameInfo.holdingsSize = 8;
4600       startedFromSetupPosition = TRUE;
4601       break;
4602     case VariantCrazyhouse:
4603     case VariantBughouse:
4604       pieces = FIDEArray;
4605       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4606       gameInfo.holdingsSize = 5;
4607       break;
4608     case VariantWildCastle:
4609       pieces = FIDEArray;
4610       /* !!?shuffle with kings guaranteed to be on d or e file */
4611       shuffleOpenings = 1;
4612       break;
4613     case VariantNoCastle:
4614       pieces = FIDEArray;
4615       nrCastlingRights = 0;
4616       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4617       /* !!?unconstrained back-rank shuffle */
4618       shuffleOpenings = 1;
4619       break;
4620     }
4621
4622     overrule = 0;
4623     if(appData.NrFiles >= 0) {
4624         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4625         gameInfo.boardWidth = appData.NrFiles;
4626     }
4627     if(appData.NrRanks >= 0) {
4628         gameInfo.boardHeight = appData.NrRanks;
4629     }
4630     if(appData.holdingsSize >= 0) {
4631         i = appData.holdingsSize;
4632         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4633         gameInfo.holdingsSize = i;
4634     }
4635     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4636     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4637         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4638
4639     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4640     if(pawnRow < 1) pawnRow = 1;
4641
4642     /* User pieceToChar list overrules defaults */
4643     if(appData.pieceToCharTable != NULL)
4644         SetCharTable(pieceToChar, appData.pieceToCharTable);
4645
4646     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4647
4648         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4649             s = (ChessSquare) 0; /* account holding counts in guard band */
4650         for( i=0; i<BOARD_HEIGHT; i++ )
4651             initialPosition[i][j] = s;
4652
4653         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4654         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4655         initialPosition[pawnRow][j] = WhitePawn;
4656         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4657         if(gameInfo.variant == VariantXiangqi) {
4658             if(j&1) {
4659                 initialPosition[pawnRow][j] = 
4660                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4661                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4662                    initialPosition[2][j] = WhiteCannon;
4663                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4664                 }
4665             }
4666         }
4667         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4668     }
4669     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4670
4671             j=BOARD_LEFT+1;
4672             initialPosition[1][j] = WhiteBishop;
4673             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4674             j=BOARD_RGHT-2;
4675             initialPosition[1][j] = WhiteRook;
4676             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4677     }
4678
4679     if( nrCastlingRights == -1) {
4680         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4681         /*       This sets default castling rights from none to normal corners   */
4682         /* Variants with other castling rights must set them themselves above    */
4683         nrCastlingRights = 6;
4684        
4685         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4686         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4687         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4688         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4689         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4690         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4691      }
4692
4693      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4694      if(gameInfo.variant == VariantGreat) { // promotion commoners
4695         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4696         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4697         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4698         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4699      }
4700   if (appData.debugMode) {
4701     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4702   }
4703     if(shuffleOpenings) {
4704         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4705         startedFromSetupPosition = TRUE;
4706     }
4707     if(startedFromPositionFile) {
4708       /* [HGM] loadPos: use PositionFile for every new game */
4709       CopyBoard(initialPosition, filePosition);
4710       for(i=0; i<nrCastlingRights; i++)
4711           castlingRights[0][i] = initialRights[i] = fileRights[i];
4712       startedFromSetupPosition = TRUE;
4713     }
4714
4715     CopyBoard(boards[0], initialPosition);
4716
4717     if(oldx != gameInfo.boardWidth ||
4718        oldy != gameInfo.boardHeight ||
4719        oldh != gameInfo.holdingsWidth
4720 #ifdef GOTHIC
4721        || oldv == VariantGothic ||        // For licensing popups
4722        gameInfo.variant == VariantGothic
4723 #endif
4724 #ifdef FALCON
4725        || oldv == VariantFalcon ||
4726        gameInfo.variant == VariantFalcon
4727 #endif
4728                                          )
4729             InitDrawingSizes(-2 ,0);
4730
4731     if (redraw)
4732       DrawPosition(TRUE, boards[currentMove]);
4733 }
4734
4735 void
4736 SendBoard(cps, moveNum)
4737      ChessProgramState *cps;
4738      int moveNum;
4739 {
4740     char message[MSG_SIZ];
4741     
4742     if (cps->useSetboard) {
4743       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4744       sprintf(message, "setboard %s\n", fen);
4745       SendToProgram(message, cps);
4746       free(fen);
4747
4748     } else {
4749       ChessSquare *bp;
4750       int i, j;
4751       /* Kludge to set black to move, avoiding the troublesome and now
4752        * deprecated "black" command.
4753        */
4754       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4755
4756       SendToProgram("edit\n", cps);
4757       SendToProgram("#\n", cps);
4758       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4759         bp = &boards[moveNum][i][BOARD_LEFT];
4760         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4761           if ((int) *bp < (int) BlackPawn) {
4762             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4763                     AAA + j, ONE + i);
4764             if(message[0] == '+' || message[0] == '~') {
4765                 sprintf(message, "%c%c%c+\n",
4766                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4767                         AAA + j, ONE + i);
4768             }
4769             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4770                 message[1] = BOARD_RGHT   - 1 - j + '1';
4771                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4772             }
4773             SendToProgram(message, cps);
4774           }
4775         }
4776       }
4777     
4778       SendToProgram("c\n", cps);
4779       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4780         bp = &boards[moveNum][i][BOARD_LEFT];
4781         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4782           if (((int) *bp != (int) EmptySquare)
4783               && ((int) *bp >= (int) BlackPawn)) {
4784             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4785                     AAA + j, ONE + i);
4786             if(message[0] == '+' || message[0] == '~') {
4787                 sprintf(message, "%c%c%c+\n",
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4789                         AAA + j, ONE + i);
4790             }
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4794             }
4795             SendToProgram(message, cps);
4796           }
4797         }
4798       }
4799     
4800       SendToProgram(".\n", cps);
4801     }
4802     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4803 }
4804
4805 int
4806 IsPromotion(fromX, fromY, toX, toY)
4807      int fromX, fromY, toX, toY;
4808 {
4809     /* [HGM] add Shogi promotions */
4810     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4811     ChessSquare piece;
4812
4813     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4814       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4815    /* [HGM] Note to self: line above also weeds out drops */
4816     piece = boards[currentMove][fromY][fromX];
4817     if(gameInfo.variant == VariantShogi) {
4818         promotionZoneSize = 3;
4819         highestPromotingPiece = (int)WhiteKing;
4820         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4821            and if in normal chess we then allow promotion to King, why not
4822            allow promotion of other piece in Shogi?                         */
4823     }
4824     if((int)piece >= BlackPawn) {
4825         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4826              return FALSE;
4827         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4828     } else {
4829         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4830            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4831     }
4832     return ( (int)piece <= highestPromotingPiece );
4833 }
4834
4835 int
4836 InPalace(row, column)
4837      int row, column;
4838 {   /* [HGM] for Xiangqi */
4839     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4840          column < (BOARD_WIDTH + 4)/2 &&
4841          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4842     return FALSE;
4843 }
4844
4845 int
4846 PieceForSquare (x, y)
4847      int x;
4848      int y;
4849 {
4850   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4851      return -1;
4852   else
4853      return boards[currentMove][y][x];
4854 }
4855
4856 int
4857 OKToStartUserMove(x, y)
4858      int x, y;
4859 {
4860     ChessSquare from_piece;
4861     int white_piece;
4862
4863     if (matchMode) return FALSE;
4864     if (gameMode == EditPosition) return TRUE;
4865
4866     if (x >= 0 && y >= 0)
4867       from_piece = boards[currentMove][y][x];
4868     else
4869       from_piece = EmptySquare;
4870
4871     if (from_piece == EmptySquare) return FALSE;
4872
4873     white_piece = (int)from_piece >= (int)WhitePawn &&
4874       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4875
4876     switch (gameMode) {
4877       case PlayFromGameFile:
4878       case AnalyzeFile:
4879       case TwoMachinesPlay:
4880       case EndOfGame:
4881         return FALSE;
4882
4883       case IcsObserving:
4884       case IcsIdle:
4885         return FALSE;
4886
4887       case MachinePlaysWhite:
4888       case IcsPlayingBlack:
4889         if (appData.zippyPlay) return FALSE;
4890         if (white_piece) {
4891             DisplayMoveError(_("You are playing Black"));
4892             return FALSE;
4893         }
4894         break;
4895
4896       case MachinePlaysBlack:
4897       case IcsPlayingWhite:
4898         if (appData.zippyPlay) return FALSE;
4899         if (!white_piece) {
4900             DisplayMoveError(_("You are playing White"));
4901             return FALSE;
4902         }
4903         break;
4904
4905       case EditGame:
4906         if (!white_piece && WhiteOnMove(currentMove)) {
4907             DisplayMoveError(_("It is White's turn"));
4908             return FALSE;
4909         }           
4910         if (white_piece && !WhiteOnMove(currentMove)) {
4911             DisplayMoveError(_("It is Black's turn"));
4912             return FALSE;
4913         }           
4914         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4915             /* Editing correspondence game history */
4916             /* Could disallow this or prompt for confirmation */
4917             cmailOldMove = -1;
4918         }
4919         if (currentMove < forwardMostMove) {
4920             /* Discarding moves */
4921             /* Could prompt for confirmation here,
4922                but I don't think that's such a good idea */
4923             forwardMostMove = currentMove;
4924         }
4925         break;
4926
4927       case BeginningOfGame:
4928         if (appData.icsActive) return FALSE;
4929         if (!appData.noChessProgram) {
4930             if (!white_piece) {
4931                 DisplayMoveError(_("You are playing White"));
4932                 return FALSE;
4933             }
4934         }
4935         break;
4936         
4937       case Training:
4938         if (!white_piece && WhiteOnMove(currentMove)) {
4939             DisplayMoveError(_("It is White's turn"));
4940             return FALSE;
4941         }           
4942         if (white_piece && !WhiteOnMove(currentMove)) {
4943             DisplayMoveError(_("It is Black's turn"));
4944             return FALSE;
4945         }           
4946         break;
4947
4948       default:
4949       case IcsExamining:
4950         break;
4951     }
4952     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4953         && gameMode != AnalyzeFile && gameMode != Training) {
4954         DisplayMoveError(_("Displayed position is not current"));
4955         return FALSE;
4956     }
4957     return TRUE;
4958 }
4959
4960 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4961 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4962 int lastLoadGameUseList = FALSE;
4963 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4964 ChessMove lastLoadGameStart = (ChessMove) 0;
4965
4966 ChessMove
4967 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4968      int fromX, fromY, toX, toY;
4969      int promoChar;
4970      Boolean captureOwn;
4971 {
4972     ChessMove moveType;
4973     ChessSquare pdown, pup;
4974
4975     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4976
4977     /* [HGM] suppress all moves into holdings area and guard band */
4978     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4979             return ImpossibleMove;
4980
4981     /* [HGM] <sameColor> moved to here from winboard.c */
4982     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
4983     pdown = boards[currentMove][fromY][fromX];
4984     pup = boards[currentMove][toY][toX];
4985     if (    gameMode != EditPosition && !captureOwn &&
4986             (WhitePawn <= pdown && pdown < BlackPawn &&
4987              WhitePawn <= pup && pup < BlackPawn  ||
4988              BlackPawn <= pdown && pdown < EmptySquare &&
4989              BlackPawn <= pup && pup < EmptySquare 
4990             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
4991                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
4992                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
4993                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
4994                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
4995         )           )
4996          return Comment;
4997
4998     /* Check if the user is playing in turn.  This is complicated because we
4999        let the user "pick up" a piece before it is his turn.  So the piece he
5000        tried to pick up may have been captured by the time he puts it down!
5001        Therefore we use the color the user is supposed to be playing in this
5002        test, not the color of the piece that is currently on the starting
5003        square---except in EditGame mode, where the user is playing both
5004        sides; fortunately there the capture race can't happen.  (It can
5005        now happen in IcsExamining mode, but that's just too bad.  The user
5006        will get a somewhat confusing message in that case.)
5007        */
5008
5009     switch (gameMode) {
5010       case PlayFromGameFile:
5011       case AnalyzeFile:
5012       case TwoMachinesPlay:
5013       case EndOfGame:
5014       case IcsObserving:
5015       case IcsIdle:
5016         /* We switched into a game mode where moves are not accepted,
5017            perhaps while the mouse button was down. */
5018         return ImpossibleMove;
5019
5020       case MachinePlaysWhite:
5021         /* User is moving for Black */
5022         if (WhiteOnMove(currentMove)) {
5023             DisplayMoveError(_("It is White's turn"));
5024             return ImpossibleMove;
5025         }
5026         break;
5027
5028       case MachinePlaysBlack:
5029         /* User is moving for White */
5030         if (!WhiteOnMove(currentMove)) {
5031             DisplayMoveError(_("It is Black's turn"));
5032             return ImpossibleMove;
5033         }
5034         break;
5035
5036       case EditGame:
5037       case IcsExamining:
5038       case BeginningOfGame:
5039       case AnalyzeMode:
5040       case Training:
5041         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5042             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5043             /* User is moving for Black */
5044             if (WhiteOnMove(currentMove)) {
5045                 DisplayMoveError(_("It is White's turn"));
5046                 return ImpossibleMove;
5047             }
5048         } else {
5049             /* User is moving for White */
5050             if (!WhiteOnMove(currentMove)) {
5051                 DisplayMoveError(_("It is Black's turn"));
5052                 return ImpossibleMove;
5053             }
5054         }
5055         break;
5056
5057       case IcsPlayingBlack:
5058         /* User is moving for Black */
5059         if (WhiteOnMove(currentMove)) {
5060             if (!appData.premove) {
5061                 DisplayMoveError(_("It is White's turn"));
5062             } else if (toX >= 0 && toY >= 0) {
5063                 premoveToX = toX;
5064                 premoveToY = toY;
5065                 premoveFromX = fromX;
5066                 premoveFromY = fromY;
5067                 premovePromoChar = promoChar;
5068                 gotPremove = 1;
5069                 if (appData.debugMode) 
5070                     fprintf(debugFP, "Got premove: fromX %d,"
5071                             "fromY %d, toX %d, toY %d\n",
5072                             fromX, fromY, toX, toY);
5073             }
5074             return ImpossibleMove;
5075         }
5076         break;
5077
5078       case IcsPlayingWhite:
5079         /* User is moving for White */
5080         if (!WhiteOnMove(currentMove)) {
5081             if (!appData.premove) {
5082                 DisplayMoveError(_("It is Black's turn"));
5083             } else if (toX >= 0 && toY >= 0) {
5084                 premoveToX = toX;
5085                 premoveToY = toY;
5086                 premoveFromX = fromX;
5087                 premoveFromY = fromY;
5088                 premovePromoChar = promoChar;
5089                 gotPremove = 1;
5090                 if (appData.debugMode) 
5091                     fprintf(debugFP, "Got premove: fromX %d,"
5092                             "fromY %d, toX %d, toY %d\n",
5093                             fromX, fromY, toX, toY);
5094             }
5095             return ImpossibleMove;
5096         }
5097         break;
5098
5099       default:
5100         break;
5101
5102       case EditPosition:
5103         /* EditPosition, empty square, or different color piece;
5104            click-click move is possible */
5105         if (toX == -2 || toY == -2) {
5106             boards[0][fromY][fromX] = EmptySquare;
5107             return AmbiguousMove;
5108         } else if (toX >= 0 && toY >= 0) {
5109             boards[0][toY][toX] = boards[0][fromY][fromX];
5110             boards[0][fromY][fromX] = EmptySquare;
5111             return AmbiguousMove;
5112         }
5113         return ImpossibleMove;
5114     }
5115
5116     /* [HGM] If move started in holdings, it means a drop */
5117     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5118          if( pup != EmptySquare ) return ImpossibleMove;
5119          if(appData.testLegality) {
5120              /* it would be more logical if LegalityTest() also figured out
5121               * which drops are legal. For now we forbid pawns on back rank.
5122               * Shogi is on its own here...
5123               */
5124              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5125                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5126                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5127          }
5128          return WhiteDrop; /* Not needed to specify white or black yet */
5129     }
5130
5131     userOfferedDraw = FALSE;
5132         
5133     /* [HGM] always test for legality, to get promotion info */
5134     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5135                           epStatus[currentMove], castlingRights[currentMove],
5136                                          fromY, fromX, toY, toX, promoChar);
5137     /* [HGM] but possibly ignore an IllegalMove result */
5138     if (appData.testLegality) {
5139         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5140             DisplayMoveError(_("Illegal move"));
5141             return ImpossibleMove;
5142         }
5143     }
5144 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5145     return moveType;
5146     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5147        function is made into one that returns an OK move type if FinishMove
5148        should be called. This to give the calling driver routine the
5149        opportunity to finish the userMove input with a promotion popup,
5150        without bothering the user with this for invalid or illegal moves */
5151
5152 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5153 }
5154
5155 /* Common tail of UserMoveEvent and DropMenuEvent */
5156 int
5157 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5158      ChessMove moveType;
5159      int fromX, fromY, toX, toY;
5160      /*char*/int promoChar;
5161 {
5162     char *bookHit = 0;
5163 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5164     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5165         // [HGM] superchess: suppress promotions to non-available piece
5166         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5167         if(WhiteOnMove(currentMove)) {
5168             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5169         } else {
5170             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5171         }
5172     }
5173
5174     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5175        move type in caller when we know the move is a legal promotion */
5176     if(moveType == NormalMove && promoChar)
5177         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5178 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5179     /* [HGM] convert drag-and-drop piece drops to standard form */
5180     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5181          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5182            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5183                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5184 //         fromX = boards[currentMove][fromY][fromX];
5185            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5186            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5187            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5188            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5189          fromY = DROP_RANK;
5190     }
5191
5192     /* [HGM] <popupFix> The following if has been moved here from
5193        UserMoveEvent(). Because it seemed to belon here (why not allow
5194        piece drops in training games?), and because it can only be
5195        performed after it is known to what we promote. */
5196     if (gameMode == Training) {
5197       /* compare the move played on the board to the next move in the
5198        * game. If they match, display the move and the opponent's response. 
5199        * If they don't match, display an error message.
5200        */
5201       int saveAnimate;
5202       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5203       CopyBoard(testBoard, boards[currentMove]);
5204       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5205
5206       if (CompareBoards(testBoard, boards[currentMove+1])) {
5207         ForwardInner(currentMove+1);
5208
5209         /* Autoplay the opponent's response.
5210          * if appData.animate was TRUE when Training mode was entered,
5211          * the response will be animated.
5212          */
5213         saveAnimate = appData.animate;
5214         appData.animate = animateTraining;
5215         ForwardInner(currentMove+1);
5216         appData.animate = saveAnimate;
5217
5218         /* check for the end of the game */
5219         if (currentMove >= forwardMostMove) {
5220           gameMode = PlayFromGameFile;
5221           ModeHighlight();
5222           SetTrainingModeOff();
5223           DisplayInformation(_("End of game"));
5224         }
5225       } else {
5226         DisplayError(_("Incorrect move"), 0);
5227       }
5228       return 1;
5229     }
5230
5231   /* Ok, now we know that the move is good, so we can kill
5232      the previous line in Analysis Mode */
5233   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5234     forwardMostMove = currentMove;
5235   }
5236
5237   /* If we need the chess program but it's dead, restart it */
5238   ResurrectChessProgram();
5239
5240   /* A user move restarts a paused game*/
5241   if (pausing)
5242     PauseEvent();
5243
5244   thinkOutput[0] = NULLCHAR;
5245
5246   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5247
5248   if (gameMode == BeginningOfGame) {
5249     if (appData.noChessProgram) {
5250       gameMode = EditGame;
5251       SetGameInfo();
5252     } else {
5253       char buf[MSG_SIZ];
5254       gameMode = MachinePlaysBlack;
5255       StartClocks();
5256       SetGameInfo();
5257       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5258       DisplayTitle(buf);
5259       if (first.sendName) {
5260         sprintf(buf, "name %s\n", gameInfo.white);
5261         SendToProgram(buf, &first);
5262       }
5263       StartClocks();
5264     }
5265     ModeHighlight();
5266   }
5267 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5268   /* Relay move to ICS or chess engine */
5269   if (appData.icsActive) {
5270     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5271         gameMode == IcsExamining) {
5272       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5273       ics_user_moved = 1;
5274     }
5275   } else {
5276     if (first.sendTime && (gameMode == BeginningOfGame ||
5277                            gameMode == MachinePlaysWhite ||
5278                            gameMode == MachinePlaysBlack)) {
5279       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5280     }
5281     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5282          // [HGM] book: if program might be playing, let it use book
5283         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5284         first.maybeThinking = TRUE;
5285     } else SendMoveToProgram(forwardMostMove-1, &first);
5286     if (currentMove == cmailOldMove + 1) {
5287       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5288     }
5289   }
5290
5291   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5292
5293   switch (gameMode) {
5294   case EditGame:
5295     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5296                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5297     case MT_NONE:
5298     case MT_CHECK:
5299       break;
5300     case MT_CHECKMATE:
5301     case MT_STAINMATE:
5302       if (WhiteOnMove(currentMove)) {
5303         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5304       } else {
5305         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5306       }
5307       break;
5308     case MT_STALEMATE:
5309       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5310       break;
5311     }
5312     break;
5313     
5314   case MachinePlaysBlack:
5315   case MachinePlaysWhite:
5316     /* disable certain menu options while machine is thinking */
5317     SetMachineThinkingEnables();
5318     break;
5319
5320   default:
5321     break;
5322   }
5323
5324   if(bookHit) { // [HGM] book: simulate book reply
5325         static char bookMove[MSG_SIZ]; // a bit generous?
5326
5327         programStats.nodes = programStats.depth = programStats.time = 
5328         programStats.score = programStats.got_only_move = 0;
5329         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5330
5331         strcpy(bookMove, "move ");
5332         strcat(bookMove, bookHit);
5333         HandleMachineMove(bookMove, &first);
5334   }
5335   return 1;
5336 }
5337
5338 void
5339 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5340      int fromX, fromY, toX, toY;
5341      int promoChar;
5342 {
5343     /* [HGM] This routine was added to allow calling of its two logical
5344        parts from other modules in the old way. Before, UserMoveEvent()
5345        automatically called FinishMove() if the move was OK, and returned
5346        otherwise. I separated the two, in order to make it possible to
5347        slip a promotion popup in between. But that it always needs two
5348        calls, to the first part, (now called UserMoveTest() ), and to
5349        FinishMove if the first part succeeded. Calls that do not need
5350        to do anything in between, can call this routine the old way. 
5351     */
5352     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5353 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5354     if(moveType == AmbiguousMove)
5355         DrawPosition(FALSE, boards[currentMove]);
5356     else if(moveType != ImpossibleMove && moveType != Comment)
5357         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5358 }
5359
5360 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5361 {
5362 //    char * hint = lastHint;
5363     FrontEndProgramStats stats;
5364
5365     stats.which = cps == &first ? 0 : 1;
5366     stats.depth = cpstats->depth;
5367     stats.nodes = cpstats->nodes;
5368     stats.score = cpstats->score;
5369     stats.time = cpstats->time;
5370     stats.pv = cpstats->movelist;
5371     stats.hint = lastHint;
5372     stats.an_move_index = 0;
5373     stats.an_move_count = 0;
5374
5375     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5376         stats.hint = cpstats->move_name;
5377         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5378         stats.an_move_count = cpstats->nr_moves;
5379     }
5380
5381     SetProgramStats( &stats );
5382 }
5383
5384 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5385 {   // [HGM] book: this routine intercepts moves to simulate book replies
5386     char *bookHit = NULL;
5387
5388     //first determine if the incoming move brings opponent into his book
5389     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5390         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5391     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5392     if(bookHit != NULL && !cps->bookSuspend) {
5393         // make sure opponent is not going to reply after receiving move to book position
5394         SendToProgram("force\n", cps);
5395         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5396     }
5397     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5398     // now arrange restart after book miss
5399     if(bookHit) {
5400         // after a book hit we never send 'go', and the code after the call to this routine
5401         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5402         char buf[MSG_SIZ];
5403         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5404         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5405         SendToProgram(buf, cps);
5406         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5407     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5408         SendToProgram("go\n", cps);
5409         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5410     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5411         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5412             SendToProgram("go\n", cps); 
5413         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5414     }
5415     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5416 }
5417
5418 char *savedMessage;
5419 ChessProgramState *savedState;
5420 void DeferredBookMove(void)
5421 {
5422         if(savedState->lastPing != savedState->lastPong)
5423                     ScheduleDelayedEvent(DeferredBookMove, 10);
5424         else
5425         HandleMachineMove(savedMessage, savedState);
5426 }
5427
5428 void
5429 HandleMachineMove(message, cps)
5430      char *message;
5431      ChessProgramState *cps;
5432 {
5433     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5434     char realname[MSG_SIZ];
5435     int fromX, fromY, toX, toY;
5436     ChessMove moveType;
5437     char promoChar;
5438     char *p;
5439     int machineWhite;
5440     char *bookHit;
5441
5442 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5443     /*
5444      * Kludge to ignore BEL characters
5445      */
5446     while (*message == '\007') message++;
5447
5448     /*
5449      * [HGM] engine debug message: ignore lines starting with '#' character
5450      */
5451     if(cps->debug && *message == '#') return;
5452
5453     /*
5454      * Look for book output
5455      */
5456     if (cps == &first && bookRequested) {
5457         if (message[0] == '\t' || message[0] == ' ') {
5458             /* Part of the book output is here; append it */
5459             strcat(bookOutput, message);
5460             strcat(bookOutput, "  \n");
5461             return;
5462         } else if (bookOutput[0] != NULLCHAR) {
5463             /* All of book output has arrived; display it */
5464             char *p = bookOutput;
5465             while (*p != NULLCHAR) {
5466                 if (*p == '\t') *p = ' ';
5467                 p++;
5468             }
5469             DisplayInformation(bookOutput);
5470             bookRequested = FALSE;
5471             /* Fall through to parse the current output */
5472         }
5473     }
5474
5475     /*
5476      * Look for machine move.
5477      */
5478     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5479         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5480     {
5481         /* This method is only useful on engines that support ping */
5482         if (cps->lastPing != cps->lastPong) {
5483           if (gameMode == BeginningOfGame) {
5484             /* Extra move from before last new; ignore */
5485             if (appData.debugMode) {
5486                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5487             }
5488           } else {
5489             if (appData.debugMode) {
5490                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5491                         cps->which, gameMode);
5492             }
5493
5494             SendToProgram("undo\n", cps);
5495           }
5496           return;
5497         }
5498
5499         switch (gameMode) {
5500           case BeginningOfGame:
5501             /* Extra move from before last reset; ignore */
5502             if (appData.debugMode) {
5503                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5504             }
5505             return;
5506
5507           case EndOfGame:
5508           case IcsIdle:
5509           default:
5510             /* Extra move after we tried to stop.  The mode test is
5511                not a reliable way of detecting this problem, but it's
5512                the best we can do on engines that don't support ping.
5513             */
5514             if (appData.debugMode) {
5515                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5516                         cps->which, gameMode);
5517             }
5518             SendToProgram("undo\n", cps);
5519             return;
5520
5521           case MachinePlaysWhite:
5522           case IcsPlayingWhite:
5523             machineWhite = TRUE;
5524             break;
5525
5526           case MachinePlaysBlack:
5527           case IcsPlayingBlack:
5528             machineWhite = FALSE;
5529             break;
5530
5531           case TwoMachinesPlay:
5532             machineWhite = (cps->twoMachinesColor[0] == 'w');
5533             break;
5534         }
5535         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5536             if (appData.debugMode) {
5537                 fprintf(debugFP,
5538                         "Ignoring move out of turn by %s, gameMode %d"
5539                         ", forwardMost %d\n",
5540                         cps->which, gameMode, forwardMostMove);
5541             }
5542             return;
5543         }
5544
5545     if (appData.debugMode) { int f = forwardMostMove;
5546         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5547                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5548     }
5549         if(cps->alphaRank) AlphaRank(machineMove, 4);
5550         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5551                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5552             /* Machine move could not be parsed; ignore it. */
5553             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5554                     machineMove, cps->which);
5555             DisplayError(buf1, 0);
5556             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5557                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5558             if (gameMode == TwoMachinesPlay) {
5559               GameEnds(machineWhite ? BlackWins : WhiteWins,
5560                        buf1, GE_XBOARD);
5561             }
5562             return;
5563         }
5564
5565         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5566         /* So we have to redo legality test with true e.p. status here,  */
5567         /* to make sure an illegal e.p. capture does not slip through,   */
5568         /* to cause a forfeit on a justified illegal-move complaint      */
5569         /* of the opponent.                                              */
5570         if( gameMode==TwoMachinesPlay && appData.testLegality
5571             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5572                                                               ) {
5573            ChessMove moveType;
5574            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5575                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5576                              fromY, fromX, toY, toX, promoChar);
5577             if (appData.debugMode) {
5578                 int i;
5579                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5580                     castlingRights[forwardMostMove][i], castlingRank[i]);
5581                 fprintf(debugFP, "castling rights\n");
5582             }
5583             if(moveType == IllegalMove) {
5584                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5585                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5586                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5587                            buf1, GE_XBOARD);
5588                 return;
5589            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5590            /* [HGM] Kludge to handle engines that send FRC-style castling
5591               when they shouldn't (like TSCP-Gothic) */
5592            switch(moveType) {
5593              case WhiteASideCastleFR:
5594              case BlackASideCastleFR:
5595                toX+=2;
5596                currentMoveString[2]++;
5597                break;
5598              case WhiteHSideCastleFR:
5599              case BlackHSideCastleFR:
5600                toX--;
5601                currentMoveString[2]--;
5602                break;
5603              default: ; // nothing to do, but suppresses warning of pedantic compilers
5604            }
5605         }
5606         hintRequested = FALSE;
5607         lastHint[0] = NULLCHAR;
5608         bookRequested = FALSE;
5609         /* Program may be pondering now */
5610         cps->maybeThinking = TRUE;
5611         if (cps->sendTime == 2) cps->sendTime = 1;
5612         if (cps->offeredDraw) cps->offeredDraw--;
5613
5614 #if ZIPPY
5615         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5616             first.initDone) {
5617           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5618           ics_user_moved = 1;
5619           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5620                 char buf[3*MSG_SIZ];
5621
5622                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5623                         programStats.score / 100.,
5624                         programStats.depth,
5625                         programStats.time / 100.,
5626                         (unsigned int)programStats.nodes,
5627                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5628                         programStats.movelist);
5629                 SendToICS(buf);
5630 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5631           }
5632         }
5633 #endif
5634         /* currentMoveString is set as a side-effect of ParseOneMove */
5635         strcpy(machineMove, currentMoveString);
5636         strcat(machineMove, "\n");
5637         strcpy(moveList[forwardMostMove], machineMove);
5638
5639         /* [AS] Save move info and clear stats for next move */
5640         pvInfoList[ forwardMostMove ].score = programStats.score;
5641         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5642         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5643         ClearProgramStats();
5644         thinkOutput[0] = NULLCHAR;
5645         hiddenThinkOutputState = 0;
5646
5647         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5648
5649         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5650         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5651             int count = 0;
5652
5653             while( count < adjudicateLossPlies ) {
5654                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5655
5656                 if( count & 1 ) {
5657                     score = -score; /* Flip score for winning side */
5658                 }
5659
5660                 if( score > adjudicateLossThreshold ) {
5661                     break;
5662                 }
5663
5664                 count++;
5665             }
5666
5667             if( count >= adjudicateLossPlies ) {
5668                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5669
5670                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5671                     "Xboard adjudication", 
5672                     GE_XBOARD );
5673
5674                 return;
5675             }
5676         }
5677
5678         if( gameMode == TwoMachinesPlay ) {
5679           // [HGM] some adjudications useful with buggy engines
5680             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5681           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5682
5683
5684             if( appData.testLegality )
5685             {   /* [HGM] Some more adjudications for obstinate engines */
5686                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5687                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5688                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5689                 static int moveCount = 6;
5690                 ChessMove result;
5691                 char *reason = NULL;
5692
5693                 /* Count what is on board. */
5694                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5695                 {   ChessSquare p = boards[forwardMostMove][i][j];
5696                     int m=i;
5697
5698                     switch((int) p)
5699                     {   /* count B,N,R and other of each side */
5700                         case WhiteKing:
5701                         case BlackKing:
5702                              NrK++; break; // [HGM] atomic: count Kings
5703                         case WhiteKnight:
5704                              NrWN++; break;
5705                         case WhiteBishop:
5706                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5707                              bishopsColor |= 1 << ((i^j)&1);
5708                              NrWB++; break;
5709                         case BlackKnight:
5710                              NrBN++; break;
5711                         case BlackBishop:
5712                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5713                              bishopsColor |= 1 << ((i^j)&1);
5714                              NrBB++; break;
5715                         case WhiteRook:
5716                              NrWR++; break;
5717                         case BlackRook:
5718                              NrBR++; break;
5719                         case WhiteQueen:
5720                              NrWQ++; break;
5721                         case BlackQueen:
5722                              NrBQ++; break;
5723                         case EmptySquare: 
5724                              break;
5725                         case BlackPawn:
5726                              m = 7-i;
5727                         case WhitePawn:
5728                              PawnAdvance += m; NrPawns++;
5729                     }
5730                     NrPieces += (p != EmptySquare);
5731                     NrW += ((int)p < (int)BlackPawn);
5732                     if(gameInfo.variant == VariantXiangqi && 
5733                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5734                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5735                         NrW -= ((int)p < (int)BlackPawn);
5736                     }
5737                 }
5738
5739                 /* Some material-based adjudications that have to be made before stalemate test */
5740                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5741                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5742                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5743                      if(appData.checkMates) {
5744                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5745                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5746                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5747                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5748                          return;
5749                      }
5750                 }
5751
5752                 /* Bare King in Shatranj (loses) or Losers (wins) */
5753                 if( NrW == 1 || NrPieces - NrW == 1) {
5754                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5755                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5756                      if(appData.checkMates) {
5757                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5758                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5759                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5760                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5761                          return;
5762                      }
5763                   } else
5764                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5765                   {    /* bare King */
5766                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5767                         if(appData.checkMates) {
5768                             /* but only adjudicate if adjudication enabled */
5769                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5770                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5771                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5772                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5773                             return;
5774                         }
5775                   }
5776                 } else bare = 1;
5777
5778
5779             // don't wait for engine to announce game end if we can judge ourselves
5780             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5781                                        castlingRights[forwardMostMove]) ) {
5782               case MT_CHECK:
5783                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5784                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5785                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5786                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5787                             checkCnt++;
5788                         if(checkCnt >= 2) {
5789                             reason = "Xboard adjudication: 3rd check";
5790                             epStatus[forwardMostMove] = EP_CHECKMATE;
5791                             break;
5792                         }
5793                     }
5794                 }
5795               case MT_NONE:
5796               default:
5797                 break;
5798               case MT_STALEMATE:
5799               case MT_STAINMATE:
5800                 reason = "Xboard adjudication: Stalemate";
5801                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5802                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5803                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5804                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5805                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5806                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5807                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5808                                                                         EP_CHECKMATE : EP_WINS);
5809                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5810                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5811                 }
5812                 break;
5813               case MT_CHECKMATE:
5814                 reason = "Xboard adjudication: Checkmate";
5815                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5816                 break;
5817             }
5818
5819                 switch(i = epStatus[forwardMostMove]) {
5820                     case EP_STALEMATE:
5821                         result = GameIsDrawn; break;
5822                     case EP_CHECKMATE:
5823                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5824                     case EP_WINS:
5825                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5826                     default:
5827                         result = (ChessMove) 0;
5828                 }
5829                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5830                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5831                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832                     GameEnds( result, reason, GE_XBOARD );
5833                     return;
5834                 }
5835
5836                 /* Next absolutely insufficient mating material. */
5837                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5838                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5839                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5840                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5841                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5842
5843                      /* always flag draws, for judging claims */
5844                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5845
5846                      if(appData.materialDraws) {
5847                          /* but only adjudicate them if adjudication enabled */
5848                          SendToProgram("force\n", cps->other); // suppress reply
5849                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5850                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5852                          return;
5853                      }
5854                 }
5855
5856                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5857                 if(NrPieces == 4 && 
5858                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5859                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5860                    || NrWN==2 || NrBN==2     /* KNNK */
5861                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5862                   ) ) {
5863                      if(--moveCount < 0 && appData.trivialDraws)
5864                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5865                           SendToProgram("force\n", cps->other); // suppress reply
5866                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5867                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5868                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5869                           return;
5870                      }
5871                 } else moveCount = 6;
5872             }
5873           }
5874 #if 1
5875     if (appData.debugMode) { int i;
5876       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5877               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5878               appData.drawRepeats);
5879       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5880            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5881
5882     }
5883 #endif
5884                 /* Check for rep-draws */
5885                 count = 0;
5886                 for(k = forwardMostMove-2;
5887                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5888                         epStatus[k] < EP_UNKNOWN &&
5889                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5890                     k-=2)
5891                 {   int rights=0;
5892                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5893                         /* compare castling rights */
5894                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5895                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5896                                 rights++; /* King lost rights, while rook still had them */
5897                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5898                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5899                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5900                                    rights++; /* but at least one rook lost them */
5901                         }
5902                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5903                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5904                                 rights++; 
5905                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5906                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5907                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5908                                    rights++;
5909                         }
5910                         if( rights == 0 && ++count > appData.drawRepeats-2
5911                             && appData.drawRepeats > 1) {
5912                              /* adjudicate after user-specified nr of repeats */
5913                              SendToProgram("force\n", cps->other); // suppress reply
5914                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5915                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5916                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5917                                 // [HGM] xiangqi: check for forbidden perpetuals
5918                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5919                                 for(m=forwardMostMove; m>k; m-=2) {
5920                                     if(MateTest(boards[m], PosFlags(m), 
5921                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5922                                         ourPerpetual = 0; // the current mover did not always check
5923                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5924                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5925                                         hisPerpetual = 0; // the opponent did not always check
5926                                 }
5927                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5928                                                                         ourPerpetual, hisPerpetual);
5929                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5930                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5931                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5932                                     return;
5933                                 }
5934                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5935                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5936                                 // Now check for perpetual chases
5937                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5938                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5939                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5940                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5941                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5942                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5943                                         return;
5944                                     }
5945                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5946                                         break; // Abort repetition-checking loop.
5947                                 }
5948                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5949                              }
5950                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5951                              return;
5952                         }
5953                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5954                              epStatus[forwardMostMove] = EP_REP_DRAW;
5955                     }
5956                 }
5957
5958                 /* Now we test for 50-move draws. Determine ply count */
5959                 count = forwardMostMove;
5960                 /* look for last irreversble move */
5961                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5962                     count--;
5963                 /* if we hit starting position, add initial plies */
5964                 if( count == backwardMostMove )
5965                     count -= initialRulePlies;
5966                 count = forwardMostMove - count; 
5967                 if( count >= 100)
5968                          epStatus[forwardMostMove] = EP_RULE_DRAW;
5969                          /* this is used to judge if draw claims are legal */
5970                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5971                          SendToProgram("force\n", cps->other); // suppress reply
5972                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5973                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5974                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5975                          return;
5976                 }
5977
5978                 /* if draw offer is pending, treat it as a draw claim
5979                  * when draw condition present, to allow engines a way to
5980                  * claim draws before making their move to avoid a race
5981                  * condition occurring after their move
5982                  */
5983                 if( cps->other->offeredDraw || cps->offeredDraw ) {
5984                          char *p = NULL;
5985                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
5986                              p = "Draw claim: 50-move rule";
5987                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
5988                              p = "Draw claim: 3-fold repetition";
5989                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
5990                              p = "Draw claim: insufficient mating material";
5991                          if( p != NULL ) {
5992                              SendToProgram("force\n", cps->other); // suppress reply
5993                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5994                              GameEnds( GameIsDrawn, p, GE_XBOARD );
5995                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996                              return;
5997                          }
5998                 }
5999
6000
6001                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6002                     SendToProgram("force\n", cps->other); // suppress reply
6003                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6004                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6005
6006                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6007
6008                     return;
6009                 }
6010         }
6011
6012         bookHit = NULL;
6013         if (gameMode == TwoMachinesPlay) {
6014             /* [HGM] relaying draw offers moved to after reception of move */
6015             /* and interpreting offer as claim if it brings draw condition */
6016             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6017                 SendToProgram("draw\n", cps->other);
6018             }
6019             if (cps->other->sendTime) {
6020                 SendTimeRemaining(cps->other,
6021                                   cps->other->twoMachinesColor[0] == 'w');
6022             }
6023             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6024             if (firstMove && !bookHit) {
6025                 firstMove = FALSE;
6026                 if (cps->other->useColors) {
6027                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6028                 }
6029                 SendToProgram("go\n", cps->other);
6030             }
6031             cps->other->maybeThinking = TRUE;
6032         }
6033
6034         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6035         
6036         if (!pausing && appData.ringBellAfterMoves) {
6037             RingBell();
6038         }
6039
6040         /* 
6041          * Reenable menu items that were disabled while
6042          * machine was thinking
6043          */
6044         if (gameMode != TwoMachinesPlay)
6045             SetUserThinkingEnables();
6046
6047         // [HGM] book: after book hit opponent has received move and is now in force mode
6048         // force the book reply into it, and then fake that it outputted this move by jumping
6049         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6050         if(bookHit) {
6051                 static char bookMove[MSG_SIZ]; // a bit generous?
6052
6053                 strcpy(bookMove, "move ");
6054                 strcat(bookMove, bookHit);
6055                 message = bookMove;
6056                 cps = cps->other;
6057                 programStats.nodes = programStats.depth = programStats.time = 
6058                 programStats.score = programStats.got_only_move = 0;
6059                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6060
6061                 if(cps->lastPing != cps->lastPong) {
6062                     savedMessage = message; // args for deferred call
6063                     savedState = cps;
6064                     ScheduleDelayedEvent(DeferredBookMove, 10);
6065                     return;
6066                 }
6067                 goto FakeBookMove;
6068         }
6069
6070         return;
6071     }
6072
6073     /* Set special modes for chess engines.  Later something general
6074      *  could be added here; for now there is just one kludge feature,
6075      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6076      *  when "xboard" is given as an interactive command.
6077      */
6078     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6079         cps->useSigint = FALSE;
6080         cps->useSigterm = FALSE;
6081     }
6082     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6083       ParseFeatures(message+8, cps);
6084       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6085     }
6086
6087     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6088      * want this, I was asked to put it in, and obliged.
6089      */
6090     if (!strncmp(message, "setboard ", 9)) {
6091         Board initial_position; int i;
6092
6093         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6094
6095         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6096             DisplayError(_("Bad FEN received from engine"), 0);
6097             return ;
6098         } else {
6099            Reset(FALSE, FALSE);
6100            CopyBoard(boards[0], initial_position);
6101            initialRulePlies = FENrulePlies;
6102            epStatus[0] = FENepStatus;
6103            for( i=0; i<nrCastlingRights; i++ )
6104                 castlingRights[0][i] = FENcastlingRights[i];
6105            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6106            else gameMode = MachinePlaysBlack;                 
6107            DrawPosition(FALSE, boards[currentMove]);
6108         }
6109         return;
6110     }
6111
6112     /*
6113      * Look for communication commands
6114      */
6115     if (!strncmp(message, "telluser ", 9)) {
6116         DisplayNote(message + 9);
6117         return;
6118     }
6119     if (!strncmp(message, "tellusererror ", 14)) {
6120         DisplayError(message + 14, 0);
6121         return;
6122     }
6123     if (!strncmp(message, "tellopponent ", 13)) {
6124       if (appData.icsActive) {
6125         if (loggedOn) {
6126           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6127           SendToICS(buf1);
6128         }
6129       } else {
6130         DisplayNote(message + 13);
6131       }
6132       return;
6133     }
6134     if (!strncmp(message, "tellothers ", 11)) {
6135       if (appData.icsActive) {
6136         if (loggedOn) {
6137           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6138           SendToICS(buf1);
6139         }
6140       }
6141       return;
6142     }
6143     if (!strncmp(message, "tellall ", 8)) {
6144       if (appData.icsActive) {
6145         if (loggedOn) {
6146           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6147           SendToICS(buf1);
6148         }
6149       } else {
6150         DisplayNote(message + 8);
6151       }
6152       return;
6153     }
6154     if (strncmp(message, "warning", 7) == 0) {
6155         /* Undocumented feature, use tellusererror in new code */
6156         DisplayError(message, 0);
6157         return;
6158     }
6159     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6160         strcpy(realname, cps->tidy);
6161         strcat(realname, " query");
6162         AskQuestion(realname, buf2, buf1, cps->pr);
6163         return;
6164     }
6165     /* Commands from the engine directly to ICS.  We don't allow these to be 
6166      *  sent until we are logged on. Crafty kibitzes have been known to 
6167      *  interfere with the login process.
6168      */
6169     if (loggedOn) {
6170         if (!strncmp(message, "tellics ", 8)) {
6171             SendToICS(message + 8);
6172             SendToICS("\n");
6173             return;
6174         }
6175         if (!strncmp(message, "tellicsnoalias ", 15)) {
6176             SendToICS(ics_prefix);
6177             SendToICS(message + 15);
6178             SendToICS("\n");
6179             return;
6180         }
6181         /* The following are for backward compatibility only */
6182         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6183             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6184             SendToICS(ics_prefix);
6185             SendToICS(message);
6186             SendToICS("\n");
6187             return;
6188         }
6189     }
6190     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6191         return;
6192     }
6193     /*
6194      * If the move is illegal, cancel it and redraw the board.
6195      * Also deal with other error cases.  Matching is rather loose
6196      * here to accommodate engines written before the spec.
6197      */
6198     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6199         strncmp(message, "Error", 5) == 0) {
6200         if (StrStr(message, "name") || 
6201             StrStr(message, "rating") || StrStr(message, "?") ||
6202             StrStr(message, "result") || StrStr(message, "board") ||
6203             StrStr(message, "bk") || StrStr(message, "computer") ||
6204             StrStr(message, "variant") || StrStr(message, "hint") ||
6205             StrStr(message, "random") || StrStr(message, "depth") ||
6206             StrStr(message, "accepted")) {
6207             return;
6208         }
6209         if (StrStr(message, "protover")) {
6210           /* Program is responding to input, so it's apparently done
6211              initializing, and this error message indicates it is
6212              protocol version 1.  So we don't need to wait any longer
6213              for it to initialize and send feature commands. */
6214           FeatureDone(cps, 1);
6215           cps->protocolVersion = 1;
6216           return;
6217         }
6218         cps->maybeThinking = FALSE;
6219
6220         if (StrStr(message, "draw")) {
6221             /* Program doesn't have "draw" command */
6222             cps->sendDrawOffers = 0;
6223             return;
6224         }
6225         if (cps->sendTime != 1 &&
6226             (StrStr(message, "time") || StrStr(message, "otim"))) {
6227           /* Program apparently doesn't have "time" or "otim" command */
6228           cps->sendTime = 0;
6229           return;
6230         }
6231         if (StrStr(message, "analyze")) {
6232             cps->analysisSupport = FALSE;
6233             cps->analyzing = FALSE;
6234             Reset(FALSE, TRUE);
6235             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6236             DisplayError(buf2, 0);
6237             return;
6238         }
6239         if (StrStr(message, "(no matching move)st")) {
6240           /* Special kludge for GNU Chess 4 only */
6241           cps->stKludge = TRUE;
6242           SendTimeControl(cps, movesPerSession, timeControl,
6243                           timeIncrement, appData.searchDepth,
6244                           searchTime);
6245           return;
6246         }
6247         if (StrStr(message, "(no matching move)sd")) {
6248           /* Special kludge for GNU Chess 4 only */
6249           cps->sdKludge = TRUE;
6250           SendTimeControl(cps, movesPerSession, timeControl,
6251                           timeIncrement, appData.searchDepth,
6252                           searchTime);
6253           return;
6254         }
6255         if (!StrStr(message, "llegal")) {
6256             return;
6257         }
6258         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6259             gameMode == IcsIdle) return;
6260         if (forwardMostMove <= backwardMostMove) return;
6261         if (pausing) PauseEvent();
6262       if(appData.forceIllegal) {
6263             // [HGM] illegal: machine refused move; force position after move into it
6264           SendToProgram("force\n", cps);
6265           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6266                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6267                 // when black is to move, while there might be nothing on a2 or black
6268                 // might already have the move. So send the board as if white has the move.
6269                 // But first we must change the stm of the engine, as it refused the last move
6270                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6271                 if(WhiteOnMove(forwardMostMove)) {
6272                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6273                     SendBoard(cps, forwardMostMove); // kludgeless board
6274                 } else {
6275                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6276                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6277                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6278                 }
6279           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6280             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6281                  gameMode == TwoMachinesPlay)
6282               SendToProgram("go\n", cps);
6283             return;
6284       } else
6285         if (gameMode == PlayFromGameFile) {
6286             /* Stop reading this game file */
6287             gameMode = EditGame;
6288             ModeHighlight();
6289         }
6290         currentMove = --forwardMostMove;
6291         DisplayMove(currentMove-1); /* before DisplayMoveError */
6292         SwitchClocks();
6293         DisplayBothClocks();
6294         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6295                 parseList[currentMove], cps->which);
6296         DisplayMoveError(buf1);
6297         DrawPosition(FALSE, boards[currentMove]);
6298
6299         /* [HGM] illegal-move claim should forfeit game when Xboard */
6300         /* only passes fully legal moves                            */
6301         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6302             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6303                                 "False illegal-move claim", GE_XBOARD );
6304         }
6305         return;
6306     }
6307     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6308         /* Program has a broken "time" command that
6309            outputs a string not ending in newline.
6310            Don't use it. */
6311         cps->sendTime = 0;
6312     }
6313     
6314     /*
6315      * If chess program startup fails, exit with an error message.
6316      * Attempts to recover here are futile.
6317      */
6318     if ((StrStr(message, "unknown host") != NULL)
6319         || (StrStr(message, "No remote directory") != NULL)
6320         || (StrStr(message, "not found") != NULL)
6321         || (StrStr(message, "No such file") != NULL)
6322         || (StrStr(message, "can't alloc") != NULL)
6323         || (StrStr(message, "Permission denied") != NULL)) {
6324
6325         cps->maybeThinking = FALSE;
6326         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6327                 cps->which, cps->program, cps->host, message);
6328         RemoveInputSource(cps->isr);
6329         DisplayFatalError(buf1, 0, 1);
6330         return;
6331     }
6332     
6333     /* 
6334      * Look for hint output
6335      */
6336     if (sscanf(message, "Hint: %s", buf1) == 1) {
6337         if (cps == &first && hintRequested) {
6338             hintRequested = FALSE;
6339             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6340                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6341                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6342                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6343                                     fromY, fromX, toY, toX, promoChar, buf1);
6344                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6345                 DisplayInformation(buf2);
6346             } else {
6347                 /* Hint move could not be parsed!? */
6348               snprintf(buf2, sizeof(buf2),
6349                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6350                         buf1, cps->which);
6351                 DisplayError(buf2, 0);
6352             }
6353         } else {
6354             strcpy(lastHint, buf1);
6355         }
6356         return;
6357     }
6358
6359     /*
6360      * Ignore other messages if game is not in progress
6361      */
6362     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6363         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6364
6365     /*
6366      * look for win, lose, draw, or draw offer
6367      */
6368     if (strncmp(message, "1-0", 3) == 0) {
6369         char *p, *q, *r = "";
6370         p = strchr(message, '{');
6371         if (p) {
6372             q = strchr(p, '}');
6373             if (q) {
6374                 *q = NULLCHAR;
6375                 r = p + 1;
6376             }
6377         }
6378         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6379         return;
6380     } else if (strncmp(message, "0-1", 3) == 0) {
6381         char *p, *q, *r = "";
6382         p = strchr(message, '{');
6383         if (p) {
6384             q = strchr(p, '}');
6385             if (q) {
6386                 *q = NULLCHAR;
6387                 r = p + 1;
6388             }
6389         }
6390         /* Kludge for Arasan 4.1 bug */
6391         if (strcmp(r, "Black resigns") == 0) {
6392             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6393             return;
6394         }
6395         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6396         return;
6397     } else if (strncmp(message, "1/2", 3) == 0) {
6398         char *p, *q, *r = "";
6399         p = strchr(message, '{');
6400         if (p) {
6401             q = strchr(p, '}');
6402             if (q) {
6403                 *q = NULLCHAR;
6404                 r = p + 1;
6405             }
6406         }
6407             
6408         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6409         return;
6410
6411     } else if (strncmp(message, "White resign", 12) == 0) {
6412         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6413         return;
6414     } else if (strncmp(message, "Black resign", 12) == 0) {
6415         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6416         return;
6417     } else if (strncmp(message, "White matches", 13) == 0 ||
6418                strncmp(message, "Black matches", 13) == 0   ) {
6419         /* [HGM] ignore GNUShogi noises */
6420         return;
6421     } else if (strncmp(message, "White", 5) == 0 &&
6422                message[5] != '(' &&
6423                StrStr(message, "Black") == NULL) {
6424         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6425         return;
6426     } else if (strncmp(message, "Black", 5) == 0 &&
6427                message[5] != '(') {
6428         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6429         return;
6430     } else if (strcmp(message, "resign") == 0 ||
6431                strcmp(message, "computer resigns") == 0) {
6432         switch (gameMode) {
6433           case MachinePlaysBlack:
6434           case IcsPlayingBlack:
6435             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6436             break;
6437           case MachinePlaysWhite:
6438           case IcsPlayingWhite:
6439             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6440             break;
6441           case TwoMachinesPlay:
6442             if (cps->twoMachinesColor[0] == 'w')
6443               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6444             else
6445               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6446             break;
6447           default:
6448             /* can't happen */
6449             break;
6450         }
6451         return;
6452     } else if (strncmp(message, "opponent mates", 14) == 0) {
6453         switch (gameMode) {
6454           case MachinePlaysBlack:
6455           case IcsPlayingBlack:
6456             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6457             break;
6458           case MachinePlaysWhite:
6459           case IcsPlayingWhite:
6460             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6461             break;
6462           case TwoMachinesPlay:
6463             if (cps->twoMachinesColor[0] == 'w')
6464               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6465             else
6466               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6467             break;
6468           default:
6469             /* can't happen */
6470             break;
6471         }
6472         return;
6473     } else if (strncmp(message, "computer mates", 14) == 0) {
6474         switch (gameMode) {
6475           case MachinePlaysBlack:
6476           case IcsPlayingBlack:
6477             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6478             break;
6479           case MachinePlaysWhite:
6480           case IcsPlayingWhite:
6481             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6482             break;
6483           case TwoMachinesPlay:
6484             if (cps->twoMachinesColor[0] == 'w')
6485               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6486             else
6487               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6488             break;
6489           default:
6490             /* can't happen */
6491             break;
6492         }
6493         return;
6494     } else if (strncmp(message, "checkmate", 9) == 0) {
6495         if (WhiteOnMove(forwardMostMove)) {
6496             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6497         } else {
6498             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6499         }
6500         return;
6501     } else if (strstr(message, "Draw") != NULL ||
6502                strstr(message, "game is a draw") != NULL) {
6503         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6504         return;
6505     } else if (strstr(message, "offer") != NULL &&
6506                strstr(message, "draw") != NULL) {
6507 #if ZIPPY
6508         if (appData.zippyPlay && first.initDone) {
6509             /* Relay offer to ICS */
6510             SendToICS(ics_prefix);
6511             SendToICS("draw\n");
6512         }
6513 #endif
6514         cps->offeredDraw = 2; /* valid until this engine moves twice */
6515         if (gameMode == TwoMachinesPlay) {
6516             if (cps->other->offeredDraw) {
6517                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6518             /* [HGM] in two-machine mode we delay relaying draw offer      */
6519             /* until after we also have move, to see if it is really claim */
6520             }
6521         } else if (gameMode == MachinePlaysWhite ||
6522                    gameMode == MachinePlaysBlack) {
6523           if (userOfferedDraw) {
6524             DisplayInformation(_("Machine accepts your draw offer"));
6525             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6526           } else {
6527             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6528           }
6529         }
6530     }
6531
6532     
6533     /*
6534      * Look for thinking output
6535      */
6536     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6537           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6538                                 ) {
6539         int plylev, mvleft, mvtot, curscore, time;
6540         char mvname[MOVE_LEN];
6541         u64 nodes; // [DM]
6542         char plyext;
6543         int ignore = FALSE;
6544         int prefixHint = FALSE;
6545         mvname[0] = NULLCHAR;
6546
6547         switch (gameMode) {
6548           case MachinePlaysBlack:
6549           case IcsPlayingBlack:
6550             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6551             break;
6552           case MachinePlaysWhite:
6553           case IcsPlayingWhite:
6554             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6555             break;
6556           case AnalyzeMode:
6557           case AnalyzeFile:
6558             break;
6559           case IcsObserving: /* [DM] icsEngineAnalyze */
6560             if (!appData.icsEngineAnalyze) ignore = TRUE;
6561             break;
6562           case TwoMachinesPlay:
6563             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6564                 ignore = TRUE;
6565             }
6566             break;
6567           default:
6568             ignore = TRUE;
6569             break;
6570         }
6571
6572         if (!ignore) {
6573             buf1[0] = NULLCHAR;
6574             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6575                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6576
6577                 if (plyext != ' ' && plyext != '\t') {
6578                     time *= 100;
6579                 }
6580
6581                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6582                 if( cps->scoreIsAbsolute && 
6583                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6584                 {
6585                     curscore = -curscore;
6586                 }
6587
6588
6589                 programStats.depth = plylev;
6590                 programStats.nodes = nodes;
6591                 programStats.time = time;
6592                 programStats.score = curscore;
6593                 programStats.got_only_move = 0;
6594
6595                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6596                         int ticklen;
6597
6598                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6599                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6600                         if(WhiteOnMove(forwardMostMove)) 
6601                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6602                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6603                 }
6604
6605                 /* Buffer overflow protection */
6606                 if (buf1[0] != NULLCHAR) {
6607                     if (strlen(buf1) >= sizeof(programStats.movelist)
6608                         && appData.debugMode) {
6609                         fprintf(debugFP,
6610                                 "PV is too long; using the first %d bytes.\n",
6611                                 sizeof(programStats.movelist) - 1);
6612                     }
6613
6614                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6615                 } else {
6616                     sprintf(programStats.movelist, " no PV\n");
6617                 }
6618
6619                 if (programStats.seen_stat) {
6620                     programStats.ok_to_send = 1;
6621                 }
6622
6623                 if (strchr(programStats.movelist, '(') != NULL) {
6624                     programStats.line_is_book = 1;
6625                     programStats.nr_moves = 0;
6626                     programStats.moves_left = 0;
6627                 } else {
6628                     programStats.line_is_book = 0;
6629                 }
6630
6631                 SendProgramStatsToFrontend( cps, &programStats );
6632
6633                 /* 
6634                     [AS] Protect the thinkOutput buffer from overflow... this
6635                     is only useful if buf1 hasn't overflowed first!
6636                 */
6637                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6638                         plylev, 
6639                         (gameMode == TwoMachinesPlay ?
6640                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6641                         ((double) curscore) / 100.0,
6642                         prefixHint ? lastHint : "",
6643                         prefixHint ? " " : "" );
6644
6645                 if( buf1[0] != NULLCHAR ) {
6646                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6647
6648                     if( strlen(buf1) > max_len ) {
6649                         if( appData.debugMode) {
6650                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6651                         }
6652                         buf1[max_len+1] = '\0';
6653                     }
6654
6655                     strcat( thinkOutput, buf1 );
6656                 }
6657
6658                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6659                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6660                     DisplayMove(currentMove - 1);
6661                     DisplayAnalysis();
6662                 }
6663                 return;
6664
6665             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6666                 /* crafty (9.25+) says "(only move) <move>"
6667                  * if there is only 1 legal move
6668                  */
6669                 sscanf(p, "(only move) %s", buf1);
6670                 sprintf(thinkOutput, "%s (only move)", buf1);
6671                 sprintf(programStats.movelist, "%s (only move)", buf1);
6672                 programStats.depth = 1;
6673                 programStats.nr_moves = 1;
6674                 programStats.moves_left = 1;
6675                 programStats.nodes = 1;
6676                 programStats.time = 1;
6677                 programStats.got_only_move = 1;
6678
6679                 /* Not really, but we also use this member to
6680                    mean "line isn't going to change" (Crafty
6681                    isn't searching, so stats won't change) */
6682                 programStats.line_is_book = 1;
6683
6684                 SendProgramStatsToFrontend( cps, &programStats );
6685                 
6686                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6687                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6688                     DisplayMove(currentMove - 1);
6689                     DisplayAnalysis();
6690                 }
6691                 return;
6692             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6693                               &time, &nodes, &plylev, &mvleft,
6694                               &mvtot, mvname) >= 5) {
6695                 /* The stat01: line is from Crafty (9.29+) in response
6696                    to the "." command */
6697                 programStats.seen_stat = 1;
6698                 cps->maybeThinking = TRUE;
6699
6700                 if (programStats.got_only_move || !appData.periodicUpdates)
6701                   return;
6702
6703                 programStats.depth = plylev;
6704                 programStats.time = time;
6705                 programStats.nodes = nodes;
6706                 programStats.moves_left = mvleft;
6707                 programStats.nr_moves = mvtot;
6708                 strcpy(programStats.move_name, mvname);
6709                 programStats.ok_to_send = 1;
6710                 programStats.movelist[0] = '\0';
6711
6712                 SendProgramStatsToFrontend( cps, &programStats );
6713
6714                 DisplayAnalysis();
6715                 return;
6716
6717             } else if (strncmp(message,"++",2) == 0) {
6718                 /* Crafty 9.29+ outputs this */
6719                 programStats.got_fail = 2;
6720                 return;
6721
6722             } else if (strncmp(message,"--",2) == 0) {
6723                 /* Crafty 9.29+ outputs this */
6724                 programStats.got_fail = 1;
6725                 return;
6726
6727             } else if (thinkOutput[0] != NULLCHAR &&
6728                        strncmp(message, "    ", 4) == 0) {
6729                 unsigned message_len;
6730
6731                 p = message;
6732                 while (*p && *p == ' ') p++;
6733
6734                 message_len = strlen( p );
6735
6736                 /* [AS] Avoid buffer overflow */
6737                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6738                     strcat(thinkOutput, " ");
6739                     strcat(thinkOutput, p);
6740                 }
6741
6742                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6743                     strcat(programStats.movelist, " ");
6744                     strcat(programStats.movelist, p);
6745                 }
6746
6747                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6748                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6749                     DisplayMove(currentMove - 1);
6750                     DisplayAnalysis();
6751                 }
6752                 return;
6753             }
6754         }
6755         else {
6756             buf1[0] = NULLCHAR;
6757
6758             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6759                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6760             {
6761                 ChessProgramStats cpstats;
6762
6763                 if (plyext != ' ' && plyext != '\t') {
6764                     time *= 100;
6765                 }
6766
6767                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6768                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6769                     curscore = -curscore;
6770                 }
6771
6772                 cpstats.depth = plylev;
6773                 cpstats.nodes = nodes;
6774                 cpstats.time = time;
6775                 cpstats.score = curscore;
6776                 cpstats.got_only_move = 0;
6777                 cpstats.movelist[0] = '\0';
6778
6779                 if (buf1[0] != NULLCHAR) {
6780                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6781                 }
6782
6783                 cpstats.ok_to_send = 0;
6784                 cpstats.line_is_book = 0;
6785                 cpstats.nr_moves = 0;
6786                 cpstats.moves_left = 0;
6787
6788                 SendProgramStatsToFrontend( cps, &cpstats );
6789             }
6790         }
6791     }
6792 }
6793
6794
6795 /* Parse a game score from the character string "game", and
6796    record it as the history of the current game.  The game
6797    score is NOT assumed to start from the standard position. 
6798    The display is not updated in any way.
6799    */
6800 void
6801 ParseGameHistory(game)
6802      char *game;
6803 {
6804     ChessMove moveType;
6805     int fromX, fromY, toX, toY, boardIndex;
6806     char promoChar;
6807     char *p, *q;
6808     char buf[MSG_SIZ];
6809
6810     if (appData.debugMode)
6811       fprintf(debugFP, "Parsing game history: %s\n", game);
6812
6813     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6814     gameInfo.site = StrSave(appData.icsHost);
6815     gameInfo.date = PGNDate();
6816     gameInfo.round = StrSave("-");
6817
6818     /* Parse out names of players */
6819     while (*game == ' ') game++;
6820     p = buf;
6821     while (*game != ' ') *p++ = *game++;
6822     *p = NULLCHAR;
6823     gameInfo.white = StrSave(buf);
6824     while (*game == ' ') game++;
6825     p = buf;
6826     while (*game != ' ' && *game != '\n') *p++ = *game++;
6827     *p = NULLCHAR;
6828     gameInfo.black = StrSave(buf);
6829
6830     /* Parse moves */
6831     boardIndex = blackPlaysFirst ? 1 : 0;
6832     yynewstr(game);
6833     for (;;) {
6834         yyboardindex = boardIndex;
6835         moveType = (ChessMove) yylex();
6836         switch (moveType) {
6837           case IllegalMove:             /* maybe suicide chess, etc. */
6838   if (appData.debugMode) {
6839     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6840     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6841     setbuf(debugFP, NULL);
6842   }
6843           case WhitePromotionChancellor:
6844           case BlackPromotionChancellor:
6845           case WhitePromotionArchbishop:
6846           case BlackPromotionArchbishop:
6847           case WhitePromotionQueen:
6848           case BlackPromotionQueen:
6849           case WhitePromotionRook:
6850           case BlackPromotionRook:
6851           case WhitePromotionBishop:
6852           case BlackPromotionBishop:
6853           case WhitePromotionKnight:
6854           case BlackPromotionKnight:
6855           case WhitePromotionKing:
6856           case BlackPromotionKing:
6857           case NormalMove:
6858           case WhiteCapturesEnPassant:
6859           case BlackCapturesEnPassant:
6860           case WhiteKingSideCastle:
6861           case WhiteQueenSideCastle:
6862           case BlackKingSideCastle:
6863           case BlackQueenSideCastle:
6864           case WhiteKingSideCastleWild:
6865           case WhiteQueenSideCastleWild:
6866           case BlackKingSideCastleWild:
6867           case BlackQueenSideCastleWild:
6868           /* PUSH Fabien */
6869           case WhiteHSideCastleFR:
6870           case WhiteASideCastleFR:
6871           case BlackHSideCastleFR:
6872           case BlackASideCastleFR:
6873           /* POP Fabien */
6874             fromX = currentMoveString[0] - AAA;
6875             fromY = currentMoveString[1] - ONE;
6876             toX = currentMoveString[2] - AAA;
6877             toY = currentMoveString[3] - ONE;
6878             promoChar = currentMoveString[4];
6879             break;
6880           case WhiteDrop:
6881           case BlackDrop:
6882             fromX = moveType == WhiteDrop ?
6883               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6884             (int) CharToPiece(ToLower(currentMoveString[0]));
6885             fromY = DROP_RANK;
6886             toX = currentMoveString[2] - AAA;
6887             toY = currentMoveString[3] - ONE;
6888             promoChar = NULLCHAR;
6889             break;
6890           case AmbiguousMove:
6891             /* bug? */
6892             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6893   if (appData.debugMode) {
6894     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6895     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6896     setbuf(debugFP, NULL);
6897   }
6898             DisplayError(buf, 0);
6899             return;
6900           case ImpossibleMove:
6901             /* bug? */
6902             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6903   if (appData.debugMode) {
6904     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6905     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6906     setbuf(debugFP, NULL);
6907   }
6908             DisplayError(buf, 0);
6909             return;
6910           case (ChessMove) 0:   /* end of file */
6911             if (boardIndex < backwardMostMove) {
6912                 /* Oops, gap.  How did that happen? */
6913                 DisplayError(_("Gap in move list"), 0);
6914                 return;
6915             }
6916             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6917             if (boardIndex > forwardMostMove) {
6918                 forwardMostMove = boardIndex;
6919             }
6920             return;
6921           case ElapsedTime:
6922             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6923                 strcat(parseList[boardIndex-1], " ");
6924                 strcat(parseList[boardIndex-1], yy_text);
6925             }
6926             continue;
6927           case Comment:
6928           case PGNTag:
6929           case NAG:
6930           default:
6931             /* ignore */
6932             continue;
6933           case WhiteWins:
6934           case BlackWins:
6935           case GameIsDrawn:
6936           case GameUnfinished:
6937             if (gameMode == IcsExamining) {
6938                 if (boardIndex < backwardMostMove) {
6939                     /* Oops, gap.  How did that happen? */
6940                     return;
6941                 }
6942                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6943                 return;
6944             }
6945             gameInfo.result = moveType;
6946             p = strchr(yy_text, '{');
6947             if (p == NULL) p = strchr(yy_text, '(');
6948             if (p == NULL) {
6949                 p = yy_text;
6950                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6951             } else {
6952                 q = strchr(p, *p == '{' ? '}' : ')');
6953                 if (q != NULL) *q = NULLCHAR;
6954                 p++;
6955             }
6956             gameInfo.resultDetails = StrSave(p);
6957             continue;
6958         }
6959         if (boardIndex >= forwardMostMove &&
6960             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6961             backwardMostMove = blackPlaysFirst ? 1 : 0;
6962             return;
6963         }
6964         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6965                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6966                                  parseList[boardIndex]);
6967         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6968         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6969         /* currentMoveString is set as a side-effect of yylex */
6970         strcpy(moveList[boardIndex], currentMoveString);
6971         strcat(moveList[boardIndex], "\n");
6972         boardIndex++;
6973         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
6974                                         castlingRights[boardIndex], &epStatus[boardIndex]);
6975         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6976                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
6977           case MT_NONE:
6978           case MT_STALEMATE:
6979           default:
6980             break;
6981           case MT_CHECK:
6982             if(gameInfo.variant != VariantShogi)
6983                 strcat(parseList[boardIndex - 1], "+");
6984             break;
6985           case MT_CHECKMATE:
6986           case MT_STAINMATE:
6987             strcat(parseList[boardIndex - 1], "#");
6988             break;
6989         }
6990     }
6991 }
6992
6993
6994 /* Apply a move to the given board  */
6995 void
6996 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
6997      int fromX, fromY, toX, toY;
6998      int promoChar;
6999      Board board;
7000      char *castling;
7001      char *ep;
7002 {
7003   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7004
7005     /* [HGM] compute & store e.p. status and castling rights for new position */
7006     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7007     { int i;
7008
7009       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7010       oldEP = *ep;
7011       *ep = EP_NONE;
7012
7013       if( board[toY][toX] != EmptySquare ) 
7014            *ep = EP_CAPTURE;  
7015
7016       if( board[fromY][fromX] == WhitePawn ) {
7017            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7018                *ep = EP_PAWN_MOVE;
7019            if( toY-fromY==2) {
7020                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7021                         gameInfo.variant != VariantBerolina || toX < fromX)
7022                       *ep = toX | berolina;
7023                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7024                         gameInfo.variant != VariantBerolina || toX > fromX) 
7025                       *ep = toX;
7026            }
7027       } else 
7028       if( board[fromY][fromX] == BlackPawn ) {
7029            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7030                *ep = EP_PAWN_MOVE; 
7031            if( toY-fromY== -2) {
7032                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7033                         gameInfo.variant != VariantBerolina || toX < fromX)
7034                       *ep = toX | berolina;
7035                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7036                         gameInfo.variant != VariantBerolina || toX > fromX) 
7037                       *ep = toX;
7038            }
7039        }
7040
7041        for(i=0; i<nrCastlingRights; i++) {
7042            if(castling[i] == fromX && castlingRank[i] == fromY ||
7043               castling[i] == toX   && castlingRank[i] == toY   
7044              ) castling[i] = -1; // revoke for moved or captured piece
7045        }
7046
7047     }
7048
7049   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7050   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7051        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7052          
7053   if (fromX == toX && fromY == toY) return;
7054
7055   if (fromY == DROP_RANK) {
7056         /* must be first */
7057         piece = board[toY][toX] = (ChessSquare) fromX;
7058   } else {
7059      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7060      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7061      if(gameInfo.variant == VariantKnightmate)
7062          king += (int) WhiteUnicorn - (int) WhiteKing;
7063
7064     /* Code added by Tord: */
7065     /* FRC castling assumed when king captures friendly rook. */
7066     if (board[fromY][fromX] == WhiteKing &&
7067              board[toY][toX] == WhiteRook) {
7068       board[fromY][fromX] = EmptySquare;
7069       board[toY][toX] = EmptySquare;
7070       if(toX > fromX) {
7071         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7072       } else {
7073         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7074       }
7075     } else if (board[fromY][fromX] == BlackKing &&
7076                board[toY][toX] == BlackRook) {
7077       board[fromY][fromX] = EmptySquare;
7078       board[toY][toX] = EmptySquare;
7079       if(toX > fromX) {
7080         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7081       } else {
7082         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7083       }
7084     /* End of code added by Tord */
7085
7086     } else if (board[fromY][fromX] == king
7087         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7088         && toY == fromY && toX > fromX+1) {
7089         board[fromY][fromX] = EmptySquare;
7090         board[toY][toX] = king;
7091         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7092         board[fromY][BOARD_RGHT-1] = EmptySquare;
7093     } else if (board[fromY][fromX] == king
7094         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7095                && toY == fromY && toX < fromX-1) {
7096         board[fromY][fromX] = EmptySquare;
7097         board[toY][toX] = king;
7098         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7099         board[fromY][BOARD_LEFT] = EmptySquare;
7100     } else if (board[fromY][fromX] == WhitePawn
7101                && toY == BOARD_HEIGHT-1
7102                && gameInfo.variant != VariantXiangqi
7103                ) {
7104         /* white pawn promotion */
7105         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7106         if (board[toY][toX] == EmptySquare) {
7107             board[toY][toX] = WhiteQueen;
7108         }
7109         if(gameInfo.variant==VariantBughouse ||
7110            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7111             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7112         board[fromY][fromX] = EmptySquare;
7113     } else if ((fromY == BOARD_HEIGHT-4)
7114                && (toX != fromX)
7115                && gameInfo.variant != VariantXiangqi
7116                && gameInfo.variant != VariantBerolina
7117                && (board[fromY][fromX] == WhitePawn)
7118                && (board[toY][toX] == EmptySquare)) {
7119         board[fromY][fromX] = EmptySquare;
7120         board[toY][toX] = WhitePawn;
7121         captured = board[toY - 1][toX];
7122         board[toY - 1][toX] = EmptySquare;
7123     } else if ((fromY == BOARD_HEIGHT-4)
7124                && (toX == fromX)
7125                && gameInfo.variant == VariantBerolina
7126                && (board[fromY][fromX] == WhitePawn)
7127                && (board[toY][toX] == EmptySquare)) {
7128         board[fromY][fromX] = EmptySquare;
7129         board[toY][toX] = WhitePawn;
7130         if(oldEP & EP_BEROLIN_A) {
7131                 captured = board[fromY][fromX-1];
7132                 board[fromY][fromX-1] = EmptySquare;
7133         }else{  captured = board[fromY][fromX+1];
7134                 board[fromY][fromX+1] = EmptySquare;
7135         }
7136     } else if (board[fromY][fromX] == king
7137         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7138                && toY == fromY && toX > fromX+1) {
7139         board[fromY][fromX] = EmptySquare;
7140         board[toY][toX] = king;
7141         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7142         board[fromY][BOARD_RGHT-1] = EmptySquare;
7143     } else if (board[fromY][fromX] == king
7144         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7145                && toY == fromY && toX < fromX-1) {
7146         board[fromY][fromX] = EmptySquare;
7147         board[toY][toX] = king;
7148         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7149         board[fromY][BOARD_LEFT] = EmptySquare;
7150     } else if (fromY == 7 && fromX == 3
7151                && board[fromY][fromX] == BlackKing
7152                && toY == 7 && toX == 5) {
7153         board[fromY][fromX] = EmptySquare;
7154         board[toY][toX] = BlackKing;
7155         board[fromY][7] = EmptySquare;
7156         board[toY][4] = BlackRook;
7157     } else if (fromY == 7 && fromX == 3
7158                && board[fromY][fromX] == BlackKing
7159                && toY == 7 && toX == 1) {
7160         board[fromY][fromX] = EmptySquare;
7161         board[toY][toX] = BlackKing;
7162         board[fromY][0] = EmptySquare;
7163         board[toY][2] = BlackRook;
7164     } else if (board[fromY][fromX] == BlackPawn
7165                && toY == 0
7166                && gameInfo.variant != VariantXiangqi
7167                ) {
7168         /* black pawn promotion */
7169         board[0][toX] = CharToPiece(ToLower(promoChar));
7170         if (board[0][toX] == EmptySquare) {
7171             board[0][toX] = BlackQueen;
7172         }
7173         if(gameInfo.variant==VariantBughouse ||
7174            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7175             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7176         board[fromY][fromX] = EmptySquare;
7177     } else if ((fromY == 3)
7178                && (toX != fromX)
7179                && gameInfo.variant != VariantXiangqi
7180                && gameInfo.variant != VariantBerolina
7181                && (board[fromY][fromX] == BlackPawn)
7182                && (board[toY][toX] == EmptySquare)) {
7183         board[fromY][fromX] = EmptySquare;
7184         board[toY][toX] = BlackPawn;
7185         captured = board[toY + 1][toX];
7186         board[toY + 1][toX] = EmptySquare;
7187     } else if ((fromY == 3)
7188                && (toX == fromX)
7189                && gameInfo.variant == VariantBerolina
7190                && (board[fromY][fromX] == BlackPawn)
7191                && (board[toY][toX] == EmptySquare)) {
7192         board[fromY][fromX] = EmptySquare;
7193         board[toY][toX] = BlackPawn;
7194         if(oldEP & EP_BEROLIN_A) {
7195                 captured = board[fromY][fromX-1];
7196                 board[fromY][fromX-1] = EmptySquare;
7197         }else{  captured = board[fromY][fromX+1];
7198                 board[fromY][fromX+1] = EmptySquare;
7199         }
7200     } else {
7201         board[toY][toX] = board[fromY][fromX];
7202         board[fromY][fromX] = EmptySquare;
7203     }
7204
7205     /* [HGM] now we promote for Shogi, if needed */
7206     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7207         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7208   }
7209
7210     if (gameInfo.holdingsWidth != 0) {
7211
7212       /* !!A lot more code needs to be written to support holdings  */
7213       /* [HGM] OK, so I have written it. Holdings are stored in the */
7214       /* penultimate board files, so they are automaticlly stored   */
7215       /* in the game history.                                       */
7216       if (fromY == DROP_RANK) {
7217         /* Delete from holdings, by decreasing count */
7218         /* and erasing image if necessary            */
7219         p = (int) fromX;
7220         if(p < (int) BlackPawn) { /* white drop */
7221              p -= (int)WhitePawn;
7222              if(p >= gameInfo.holdingsSize) p = 0;
7223              if(--board[p][BOARD_WIDTH-2] == 0)
7224                   board[p][BOARD_WIDTH-1] = EmptySquare;
7225         } else {                  /* black drop */
7226              p -= (int)BlackPawn;
7227              if(p >= gameInfo.holdingsSize) p = 0;
7228              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7229                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7230         }
7231       }
7232       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7233           && gameInfo.variant != VariantBughouse        ) {
7234         /* [HGM] holdings: Add to holdings, if holdings exist */
7235         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7236                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7237                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7238         }
7239         p = (int) captured;
7240         if (p >= (int) BlackPawn) {
7241           p -= (int)BlackPawn;
7242           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7243                   /* in Shogi restore piece to its original  first */
7244                   captured = (ChessSquare) (DEMOTED captured);
7245                   p = DEMOTED p;
7246           }
7247           p = PieceToNumber((ChessSquare)p);
7248           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7249           board[p][BOARD_WIDTH-2]++;
7250           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7251         } else {
7252           p -= (int)WhitePawn;
7253           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7254                   captured = (ChessSquare) (DEMOTED captured);
7255                   p = DEMOTED p;
7256           }
7257           p = PieceToNumber((ChessSquare)p);
7258           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7259           board[BOARD_HEIGHT-1-p][1]++;
7260           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7261         }
7262       }
7263
7264     } else if (gameInfo.variant == VariantAtomic) {
7265       if (captured != EmptySquare) {
7266         int y, x;
7267         for (y = toY-1; y <= toY+1; y++) {
7268           for (x = toX-1; x <= toX+1; x++) {
7269             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7270                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7271               board[y][x] = EmptySquare;
7272             }
7273           }
7274         }
7275         board[toY][toX] = EmptySquare;
7276       }
7277     }
7278     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7279         /* [HGM] Shogi promotions */
7280         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7281     }
7282
7283     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7284                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7285         // [HGM] superchess: take promotion piece out of holdings
7286         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7287         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7288             if(!--board[k][BOARD_WIDTH-2])
7289                 board[k][BOARD_WIDTH-1] = EmptySquare;
7290         } else {
7291             if(!--board[BOARD_HEIGHT-1-k][1])
7292                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7293         }
7294     }
7295
7296 }
7297
7298 /* Updates forwardMostMove */
7299 void
7300 MakeMove(fromX, fromY, toX, toY, promoChar)
7301      int fromX, fromY, toX, toY;
7302      int promoChar;
7303 {
7304 //    forwardMostMove++; // [HGM] bare: moved downstream
7305
7306     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7307         int timeLeft; static int lastLoadFlag=0; int king, piece;
7308         piece = boards[forwardMostMove][fromY][fromX];
7309         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7310         if(gameInfo.variant == VariantKnightmate)
7311             king += (int) WhiteUnicorn - (int) WhiteKing;
7312         if(forwardMostMove == 0) {
7313             if(blackPlaysFirst) 
7314                 fprintf(serverMoves, "%s;", second.tidy);
7315             fprintf(serverMoves, "%s;", first.tidy);
7316             if(!blackPlaysFirst) 
7317                 fprintf(serverMoves, "%s;", second.tidy);
7318         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7319         lastLoadFlag = loadFlag;
7320         // print base move
7321         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7322         // print castling suffix
7323         if( toY == fromY && piece == king ) {
7324             if(toX-fromX > 1)
7325                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7326             if(fromX-toX >1)
7327                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7328         }
7329         // e.p. suffix
7330         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7331              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7332              boards[forwardMostMove][toY][toX] == EmptySquare
7333              && fromX != toX )
7334                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7335         // promotion suffix
7336         if(promoChar != NULLCHAR)
7337                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7338         if(!loadFlag) {
7339             fprintf(serverMoves, "/%d/%d",
7340                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7341             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7342             else                      timeLeft = blackTimeRemaining/1000;
7343             fprintf(serverMoves, "/%d", timeLeft);
7344         }
7345         fflush(serverMoves);
7346     }
7347
7348     if (forwardMostMove+1 >= MAX_MOVES) {
7349       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7350                         0, 1);
7351       return;
7352     }
7353     if (commentList[forwardMostMove+1] != NULL) {
7354         free(commentList[forwardMostMove+1]);
7355         commentList[forwardMostMove+1] = NULL;
7356     }
7357     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7358     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7359     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7360                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7361     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7362     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7363     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7364     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7365     gameInfo.result = GameUnfinished;
7366     if (gameInfo.resultDetails != NULL) {
7367         free(gameInfo.resultDetails);
7368         gameInfo.resultDetails = NULL;
7369     }
7370     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7371                               moveList[forwardMostMove - 1]);
7372     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7373                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7374                              fromY, fromX, toY, toX, promoChar,
7375                              parseList[forwardMostMove - 1]);
7376     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7377                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7378                             castlingRights[forwardMostMove]) ) {
7379       case MT_NONE:
7380       case MT_STALEMATE:
7381       default:
7382         break;
7383       case MT_CHECK:
7384         if(gameInfo.variant != VariantShogi)
7385             strcat(parseList[forwardMostMove - 1], "+");
7386         break;
7387       case MT_CHECKMATE:
7388       case MT_STAINMATE:
7389         strcat(parseList[forwardMostMove - 1], "#");
7390         break;
7391     }
7392     if (appData.debugMode) {
7393         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7394     }
7395
7396 }
7397
7398 /* Updates currentMove if not pausing */
7399 void
7400 ShowMove(fromX, fromY, toX, toY)
7401 {
7402     int instant = (gameMode == PlayFromGameFile) ?
7403         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7404     if(appData.noGUI) return;
7405     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7406         if (!instant) {
7407             if (forwardMostMove == currentMove + 1) {
7408                 AnimateMove(boards[forwardMostMove - 1],
7409                             fromX, fromY, toX, toY);
7410             }
7411             if (appData.highlightLastMove) {
7412                 SetHighlights(fromX, fromY, toX, toY);
7413             }
7414         }
7415         currentMove = forwardMostMove;
7416     }
7417
7418     if (instant) return;
7419
7420     DisplayMove(currentMove - 1);
7421     DrawPosition(FALSE, boards[currentMove]);
7422     DisplayBothClocks();
7423     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7424 }
7425
7426 void SendEgtPath(ChessProgramState *cps)
7427 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7428         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7429
7430         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7431
7432         while(*p) {
7433             char c, *q = name+1, *r, *s;
7434
7435             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7436             while(*p && *p != ',') *q++ = *p++;
7437             *q++ = ':'; *q = 0;
7438             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7439                 strcmp(name, ",nalimov:") == 0 ) {
7440                 // take nalimov path from the menu-changeable option first, if it is defined
7441                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7442                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7443             } else
7444             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7445                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7446                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7447                 s = r = StrStr(s, ":") + 1; // beginning of path info
7448                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7449                 c = *r; *r = 0;             // temporarily null-terminate path info
7450                     *--q = 0;               // strip of trailig ':' from name
7451                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7452                 *r = c;
7453                 SendToProgram(buf,cps);     // send egtbpath command for this format
7454             }
7455             if(*p == ',') p++; // read away comma to position for next format name
7456         }
7457 }
7458
7459 void
7460 InitChessProgram(cps, setup)
7461      ChessProgramState *cps;
7462      int setup; /* [HGM] needed to setup FRC opening position */
7463 {
7464     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7465     if (appData.noChessProgram) return;
7466     hintRequested = FALSE;
7467     bookRequested = FALSE;
7468
7469     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7470     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7471     if(cps->memSize) { /* [HGM] memory */
7472         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7473         SendToProgram(buf, cps);
7474     }
7475     SendEgtPath(cps); /* [HGM] EGT */
7476     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7477         sprintf(buf, "cores %d\n", appData.smpCores);
7478         SendToProgram(buf, cps);
7479     }
7480
7481     SendToProgram(cps->initString, cps);
7482     if (gameInfo.variant != VariantNormal &&
7483         gameInfo.variant != VariantLoadable
7484         /* [HGM] also send variant if board size non-standard */
7485         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7486                                             ) {
7487       char *v = VariantName(gameInfo.variant);
7488       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7489         /* [HGM] in protocol 1 we have to assume all variants valid */
7490         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7491         DisplayFatalError(buf, 0, 1);
7492         return;
7493       }
7494
7495       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7496       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7497       if( gameInfo.variant == VariantXiangqi )
7498            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7499       if( gameInfo.variant == VariantShogi )
7500            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7501       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7502            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7503       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7504                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7505            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7506       if( gameInfo.variant == VariantCourier )
7507            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7508       if( gameInfo.variant == VariantSuper )
7509            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7510       if( gameInfo.variant == VariantGreat )
7511            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7512
7513       if(overruled) {
7514            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7515                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7516            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7517            if(StrStr(cps->variants, b) == NULL) { 
7518                // specific sized variant not known, check if general sizing allowed
7519                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7520                    if(StrStr(cps->variants, "boardsize") == NULL) {
7521                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7522                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7523                        DisplayFatalError(buf, 0, 1);
7524                        return;
7525                    }
7526                    /* [HGM] here we really should compare with the maximum supported board size */
7527                }
7528            }
7529       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7530       sprintf(buf, "variant %s\n", b);
7531       SendToProgram(buf, cps);
7532     }
7533     currentlyInitializedVariant = gameInfo.variant;
7534
7535     /* [HGM] send opening position in FRC to first engine */
7536     if(setup) {
7537           SendToProgram("force\n", cps);
7538           SendBoard(cps, 0);
7539           /* engine is now in force mode! Set flag to wake it up after first move. */
7540           setboardSpoiledMachineBlack = 1;
7541     }
7542
7543     if (cps->sendICS) {
7544       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7545       SendToProgram(buf, cps);
7546     }
7547     cps->maybeThinking = FALSE;
7548     cps->offeredDraw = 0;
7549     if (!appData.icsActive) {
7550         SendTimeControl(cps, movesPerSession, timeControl,
7551                         timeIncrement, appData.searchDepth,
7552                         searchTime);
7553     }
7554     if (appData.showThinking 
7555         // [HGM] thinking: four options require thinking output to be sent
7556         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7557                                 ) {
7558         SendToProgram("post\n", cps);
7559     }
7560     SendToProgram("hard\n", cps);
7561     if (!appData.ponderNextMove) {
7562         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7563            it without being sure what state we are in first.  "hard"
7564            is not a toggle, so that one is OK.
7565          */
7566         SendToProgram("easy\n", cps);
7567     }
7568     if (cps->usePing) {
7569       sprintf(buf, "ping %d\n", ++cps->lastPing);
7570       SendToProgram(buf, cps);
7571     }
7572     cps->initDone = TRUE;
7573 }   
7574
7575
7576 void
7577 StartChessProgram(cps)
7578      ChessProgramState *cps;
7579 {
7580     char buf[MSG_SIZ];
7581     int err;
7582
7583     if (appData.noChessProgram) return;
7584     cps->initDone = FALSE;
7585
7586     if (strcmp(cps->host, "localhost") == 0) {
7587         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7588     } else if (*appData.remoteShell == NULLCHAR) {
7589         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7590     } else {
7591         if (*appData.remoteUser == NULLCHAR) {
7592           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7593                     cps->program);
7594         } else {
7595           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7596                     cps->host, appData.remoteUser, cps->program);
7597         }
7598         err = StartChildProcess(buf, "", &cps->pr);
7599     }
7600     
7601     if (err != 0) {
7602         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7603         DisplayFatalError(buf, err, 1);
7604         cps->pr = NoProc;
7605         cps->isr = NULL;
7606         return;
7607     }
7608     
7609     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7610     if (cps->protocolVersion > 1) {
7611       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7612       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7613       cps->comboCnt = 0;  //                and values of combo boxes
7614       SendToProgram(buf, cps);
7615     } else {
7616       SendToProgram("xboard\n", cps);
7617     }
7618 }
7619
7620
7621 void
7622 TwoMachinesEventIfReady P((void))
7623 {
7624   if (first.lastPing != first.lastPong) {
7625     DisplayMessage("", _("Waiting for first chess program"));
7626     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7627     return;
7628   }
7629   if (second.lastPing != second.lastPong) {
7630     DisplayMessage("", _("Waiting for second chess program"));
7631     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7632     return;
7633   }
7634   ThawUI();
7635   TwoMachinesEvent();
7636 }
7637
7638 void
7639 NextMatchGame P((void))
7640 {
7641     int index; /* [HGM] autoinc: step lod index during match */
7642     Reset(FALSE, TRUE);
7643     if (*appData.loadGameFile != NULLCHAR) {
7644         index = appData.loadGameIndex;
7645         if(index < 0) { // [HGM] autoinc
7646             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7647             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7648         } 
7649         LoadGameFromFile(appData.loadGameFile,
7650                          index,
7651                          appData.loadGameFile, FALSE);
7652     } else if (*appData.loadPositionFile != NULLCHAR) {
7653         index = appData.loadPositionIndex;
7654         if(index < 0) { // [HGM] autoinc
7655             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7656             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7657         } 
7658         LoadPositionFromFile(appData.loadPositionFile,
7659                              index,
7660                              appData.loadPositionFile);
7661     }
7662     TwoMachinesEventIfReady();
7663 }
7664
7665 void UserAdjudicationEvent( int result )
7666 {
7667     ChessMove gameResult = GameIsDrawn;
7668
7669     if( result > 0 ) {
7670         gameResult = WhiteWins;
7671     }
7672     else if( result < 0 ) {
7673         gameResult = BlackWins;
7674     }
7675
7676     if( gameMode == TwoMachinesPlay ) {
7677         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7678     }
7679 }
7680
7681
7682 // [HGM] save: calculate checksum of game to make games easily identifiable
7683 int StringCheckSum(char *s)
7684 {
7685         int i = 0;
7686         if(s==NULL) return 0;
7687         while(*s) i = i*259 + *s++;
7688         return i;
7689 }
7690
7691 int GameCheckSum()
7692 {
7693         int i, sum=0;
7694         for(i=backwardMostMove; i<forwardMostMove; i++) {
7695                 sum += pvInfoList[i].depth;
7696                 sum += StringCheckSum(parseList[i]);
7697                 sum += StringCheckSum(commentList[i]);
7698                 sum *= 261;
7699         }
7700         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7701         return sum + StringCheckSum(commentList[i]);
7702 } // end of save patch
7703
7704 void
7705 GameEnds(result, resultDetails, whosays)
7706      ChessMove result;
7707      char *resultDetails;
7708      int whosays;
7709 {
7710     GameMode nextGameMode;
7711     int isIcsGame;
7712     char buf[MSG_SIZ];
7713
7714     if(endingGame) return; /* [HGM] crash: forbid recursion */
7715     endingGame = 1;
7716
7717     if (appData.debugMode) {
7718       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7719               result, resultDetails ? resultDetails : "(null)", whosays);
7720     }
7721
7722     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7723         /* If we are playing on ICS, the server decides when the
7724            game is over, but the engine can offer to draw, claim 
7725            a draw, or resign. 
7726          */
7727 #if ZIPPY
7728         if (appData.zippyPlay && first.initDone) {
7729             if (result == GameIsDrawn) {
7730                 /* In case draw still needs to be claimed */
7731                 SendToICS(ics_prefix);
7732                 SendToICS("draw\n");
7733             } else if (StrCaseStr(resultDetails, "resign")) {
7734                 SendToICS(ics_prefix);
7735                 SendToICS("resign\n");
7736             }
7737         }
7738 #endif
7739         endingGame = 0; /* [HGM] crash */
7740         return;
7741     }
7742
7743     /* If we're loading the game from a file, stop */
7744     if (whosays == GE_FILE) {
7745       (void) StopLoadGameTimer();
7746       gameFileFP = NULL;
7747     }
7748
7749     /* Cancel draw offers */
7750     first.offeredDraw = second.offeredDraw = 0;
7751
7752     /* If this is an ICS game, only ICS can really say it's done;
7753        if not, anyone can. */
7754     isIcsGame = (gameMode == IcsPlayingWhite || 
7755                  gameMode == IcsPlayingBlack || 
7756                  gameMode == IcsObserving    || 
7757                  gameMode == IcsExamining);
7758
7759     if (!isIcsGame || whosays == GE_ICS) {
7760         /* OK -- not an ICS game, or ICS said it was done */
7761         StopClocks();
7762         if (!isIcsGame && !appData.noChessProgram) 
7763           SetUserThinkingEnables();
7764     
7765         /* [HGM] if a machine claims the game end we verify this claim */
7766         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7767             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7768                 char claimer;
7769                 ChessMove trueResult = (ChessMove) -1;
7770
7771                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7772                                             first.twoMachinesColor[0] :
7773                                             second.twoMachinesColor[0] ;
7774
7775                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7776                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7777                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7778                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7779                 } else
7780                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7781                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7782                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7783                 } else
7784                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7785                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7786                 }
7787
7788                 // now verify win claims, but not in drop games, as we don't understand those yet
7789                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7790                                                  || gameInfo.variant == VariantGreat) &&
7791                     (result == WhiteWins && claimer == 'w' ||
7792                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7793                       if (appData.debugMode) {
7794                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7795                                 result, epStatus[forwardMostMove], forwardMostMove);
7796                       }
7797                       if(result != trueResult) {
7798                               sprintf(buf, "False win claim: '%s'", resultDetails);
7799                               result = claimer == 'w' ? BlackWins : WhiteWins;
7800                               resultDetails = buf;
7801                       }
7802                 } else
7803                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7804                     && (forwardMostMove <= backwardMostMove ||
7805                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7806                         (claimer=='b')==(forwardMostMove&1))
7807                                                                                   ) {
7808                       /* [HGM] verify: draws that were not flagged are false claims */
7809                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7810                       result = claimer == 'w' ? BlackWins : WhiteWins;
7811                       resultDetails = buf;
7812                 }
7813                 /* (Claiming a loss is accepted no questions asked!) */
7814             }
7815             /* [HGM] bare: don't allow bare King to win */
7816             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7817                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7818                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7819                && result != GameIsDrawn)
7820             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7821                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7822                         int p = (int)boards[forwardMostMove][i][j] - color;
7823                         if(p >= 0 && p <= (int)WhiteKing) k++;
7824                 }
7825                 if (appData.debugMode) {
7826                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7827                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7828                 }
7829                 if(k <= 1) {
7830                         result = GameIsDrawn;
7831                         sprintf(buf, "%s but bare king", resultDetails);
7832                         resultDetails = buf;
7833                 }
7834             }
7835         }
7836
7837
7838         if(serverMoves != NULL && !loadFlag) { char c = '=';
7839             if(result==WhiteWins) c = '+';
7840             if(result==BlackWins) c = '-';
7841             if(resultDetails != NULL)
7842                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7843         }
7844         if (resultDetails != NULL) {
7845             gameInfo.result = result;
7846             gameInfo.resultDetails = StrSave(resultDetails);
7847
7848             /* display last move only if game was not loaded from file */
7849             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7850                 DisplayMove(currentMove - 1);
7851     
7852             if (forwardMostMove != 0) {
7853                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7854                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7855                                                                 ) {
7856                     if (*appData.saveGameFile != NULLCHAR) {
7857                         SaveGameToFile(appData.saveGameFile, TRUE);
7858                     } else if (appData.autoSaveGames) {
7859                         AutoSaveGame();
7860                     }
7861                     if (*appData.savePositionFile != NULLCHAR) {
7862                         SavePositionToFile(appData.savePositionFile);
7863                     }
7864                 }
7865             }
7866
7867             /* Tell program how game ended in case it is learning */
7868             /* [HGM] Moved this to after saving the PGN, just in case */
7869             /* engine died and we got here through time loss. In that */
7870             /* case we will get a fatal error writing the pipe, which */
7871             /* would otherwise lose us the PGN.                       */
7872             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7873             /* output during GameEnds should never be fatal anymore   */
7874             if (gameMode == MachinePlaysWhite ||
7875                 gameMode == MachinePlaysBlack ||
7876                 gameMode == TwoMachinesPlay ||
7877                 gameMode == IcsPlayingWhite ||
7878                 gameMode == IcsPlayingBlack ||
7879                 gameMode == BeginningOfGame) {
7880                 char buf[MSG_SIZ];
7881                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7882                         resultDetails);
7883                 if (first.pr != NoProc) {
7884                     SendToProgram(buf, &first);
7885                 }
7886                 if (second.pr != NoProc &&
7887                     gameMode == TwoMachinesPlay) {
7888                     SendToProgram(buf, &second);
7889                 }
7890             }
7891         }
7892
7893         if (appData.icsActive) {
7894             if (appData.quietPlay &&
7895                 (gameMode == IcsPlayingWhite ||
7896                  gameMode == IcsPlayingBlack)) {
7897                 SendToICS(ics_prefix);
7898                 SendToICS("set shout 1\n");
7899             }
7900             nextGameMode = IcsIdle;
7901             ics_user_moved = FALSE;
7902             /* clean up premove.  It's ugly when the game has ended and the
7903              * premove highlights are still on the board.
7904              */
7905             if (gotPremove) {
7906               gotPremove = FALSE;
7907               ClearPremoveHighlights();
7908               DrawPosition(FALSE, boards[currentMove]);
7909             }
7910             if (whosays == GE_ICS) {
7911                 switch (result) {
7912                 case WhiteWins:
7913                     if (gameMode == IcsPlayingWhite)
7914                         PlayIcsWinSound();
7915                     else if(gameMode == IcsPlayingBlack)
7916                         PlayIcsLossSound();
7917                     break;
7918                 case BlackWins:
7919                     if (gameMode == IcsPlayingBlack)
7920                         PlayIcsWinSound();
7921                     else if(gameMode == IcsPlayingWhite)
7922                         PlayIcsLossSound();
7923                     break;
7924                 case GameIsDrawn:
7925                     PlayIcsDrawSound();
7926                     break;
7927                 default:
7928                     PlayIcsUnfinishedSound();
7929                 }
7930             }
7931         } else if (gameMode == EditGame ||
7932                    gameMode == PlayFromGameFile || 
7933                    gameMode == AnalyzeMode || 
7934                    gameMode == AnalyzeFile) {
7935             nextGameMode = gameMode;
7936         } else {
7937             nextGameMode = EndOfGame;
7938         }
7939         pausing = FALSE;
7940         ModeHighlight();
7941     } else {
7942         nextGameMode = gameMode;
7943     }
7944
7945     if (appData.noChessProgram) {
7946         gameMode = nextGameMode;
7947         ModeHighlight();
7948         endingGame = 0; /* [HGM] crash */
7949         return;
7950     }
7951
7952     if (first.reuse) {
7953         /* Put first chess program into idle state */
7954         if (first.pr != NoProc &&
7955             (gameMode == MachinePlaysWhite ||
7956              gameMode == MachinePlaysBlack ||
7957              gameMode == TwoMachinesPlay ||
7958              gameMode == IcsPlayingWhite ||
7959              gameMode == IcsPlayingBlack ||
7960              gameMode == BeginningOfGame)) {
7961             SendToProgram("force\n", &first);
7962             if (first.usePing) {
7963               char buf[MSG_SIZ];
7964               sprintf(buf, "ping %d\n", ++first.lastPing);
7965               SendToProgram(buf, &first);
7966             }
7967         }
7968     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7969         /* Kill off first chess program */
7970         if (first.isr != NULL)
7971           RemoveInputSource(first.isr);
7972         first.isr = NULL;
7973     
7974         if (first.pr != NoProc) {
7975             ExitAnalyzeMode();
7976             DoSleep( appData.delayBeforeQuit );
7977             SendToProgram("quit\n", &first);
7978             DoSleep( appData.delayAfterQuit );
7979             DestroyChildProcess(first.pr, first.useSigterm);
7980         }
7981         first.pr = NoProc;
7982     }
7983     if (second.reuse) {
7984         /* Put second chess program into idle state */
7985         if (second.pr != NoProc &&
7986             gameMode == TwoMachinesPlay) {
7987             SendToProgram("force\n", &second);
7988             if (second.usePing) {
7989               char buf[MSG_SIZ];
7990               sprintf(buf, "ping %d\n", ++second.lastPing);
7991               SendToProgram(buf, &second);
7992             }
7993         }
7994     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7995         /* Kill off second chess program */
7996         if (second.isr != NULL)
7997           RemoveInputSource(second.isr);
7998         second.isr = NULL;
7999     
8000         if (second.pr != NoProc) {
8001             DoSleep( appData.delayBeforeQuit );
8002             SendToProgram("quit\n", &second);
8003             DoSleep( appData.delayAfterQuit );
8004             DestroyChildProcess(second.pr, second.useSigterm);
8005         }
8006         second.pr = NoProc;
8007     }
8008
8009     if (matchMode && gameMode == TwoMachinesPlay) {
8010         switch (result) {
8011         case WhiteWins:
8012           if (first.twoMachinesColor[0] == 'w') {
8013             first.matchWins++;
8014           } else {
8015             second.matchWins++;
8016           }
8017           break;
8018         case BlackWins:
8019           if (first.twoMachinesColor[0] == 'b') {
8020             first.matchWins++;
8021           } else {
8022             second.matchWins++;
8023           }
8024           break;
8025         default:
8026           break;
8027         }
8028         if (matchGame < appData.matchGames) {
8029             char *tmp;
8030             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8031                 tmp = first.twoMachinesColor;
8032                 first.twoMachinesColor = second.twoMachinesColor;
8033                 second.twoMachinesColor = tmp;
8034             }
8035             gameMode = nextGameMode;
8036             matchGame++;
8037             if(appData.matchPause>10000 || appData.matchPause<10)
8038                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8039             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8040             endingGame = 0; /* [HGM] crash */
8041             return;
8042         } else {
8043             char buf[MSG_SIZ];
8044             gameMode = nextGameMode;
8045             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8046                     first.tidy, second.tidy,
8047                     first.matchWins, second.matchWins,
8048                     appData.matchGames - (first.matchWins + second.matchWins));
8049             DisplayFatalError(buf, 0, 0);
8050         }
8051     }
8052     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8053         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8054       ExitAnalyzeMode();
8055     gameMode = nextGameMode;
8056     ModeHighlight();
8057     endingGame = 0;  /* [HGM] crash */
8058 }
8059
8060 /* Assumes program was just initialized (initString sent).
8061    Leaves program in force mode. */
8062 void
8063 FeedMovesToProgram(cps, upto) 
8064      ChessProgramState *cps;
8065      int upto;
8066 {
8067     int i;
8068     
8069     if (appData.debugMode)
8070       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8071               startedFromSetupPosition ? "position and " : "",
8072               backwardMostMove, upto, cps->which);
8073     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8074         // [HGM] variantswitch: make engine aware of new variant
8075         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8076                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8077         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8078         SendToProgram(buf, cps);
8079         currentlyInitializedVariant = gameInfo.variant;
8080     }
8081     SendToProgram("force\n", cps);
8082     if (startedFromSetupPosition) {
8083         SendBoard(cps, backwardMostMove);
8084     if (appData.debugMode) {
8085         fprintf(debugFP, "feedMoves\n");
8086     }
8087     }
8088     for (i = backwardMostMove; i < upto; i++) {
8089         SendMoveToProgram(i, cps);
8090     }
8091 }
8092
8093
8094 void
8095 ResurrectChessProgram()
8096 {
8097      /* The chess program may have exited.
8098         If so, restart it and feed it all the moves made so far. */
8099
8100     if (appData.noChessProgram || first.pr != NoProc) return;
8101     
8102     StartChessProgram(&first);
8103     InitChessProgram(&first, FALSE);
8104     FeedMovesToProgram(&first, currentMove);
8105
8106     if (!first.sendTime) {
8107         /* can't tell gnuchess what its clock should read,
8108            so we bow to its notion. */
8109         ResetClocks();
8110         timeRemaining[0][currentMove] = whiteTimeRemaining;
8111         timeRemaining[1][currentMove] = blackTimeRemaining;
8112     }
8113
8114     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8115                 appData.icsEngineAnalyze) && first.analysisSupport) {
8116       SendToProgram("analyze\n", &first);
8117       first.analyzing = TRUE;
8118     }
8119 }
8120
8121 /*
8122  * Button procedures
8123  */
8124 void
8125 Reset(redraw, init)
8126      int redraw, init;
8127 {
8128     int i;
8129
8130     if (appData.debugMode) {
8131         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8132                 redraw, init, gameMode);
8133     }
8134     pausing = pauseExamInvalid = FALSE;
8135     startedFromSetupPosition = blackPlaysFirst = FALSE;
8136     firstMove = TRUE;
8137     whiteFlag = blackFlag = FALSE;
8138     userOfferedDraw = FALSE;
8139     hintRequested = bookRequested = FALSE;
8140     first.maybeThinking = FALSE;
8141     second.maybeThinking = FALSE;
8142     first.bookSuspend = FALSE; // [HGM] book
8143     second.bookSuspend = FALSE;
8144     thinkOutput[0] = NULLCHAR;
8145     lastHint[0] = NULLCHAR;
8146     ClearGameInfo(&gameInfo);
8147     gameInfo.variant = StringToVariant(appData.variant);
8148     ics_user_moved = ics_clock_paused = FALSE;
8149     ics_getting_history = H_FALSE;
8150     ics_gamenum = -1;
8151     white_holding[0] = black_holding[0] = NULLCHAR;
8152     ClearProgramStats();
8153     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8154     
8155     ResetFrontEnd();
8156     ClearHighlights();
8157     flipView = appData.flipView;
8158     ClearPremoveHighlights();
8159     gotPremove = FALSE;
8160     alarmSounded = FALSE;
8161
8162     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8163     if(appData.serverMovesName != NULL) {
8164         /* [HGM] prepare to make moves file for broadcasting */
8165         clock_t t = clock();
8166         if(serverMoves != NULL) fclose(serverMoves);
8167         serverMoves = fopen(appData.serverMovesName, "r");
8168         if(serverMoves != NULL) {
8169             fclose(serverMoves);
8170             /* delay 15 sec before overwriting, so all clients can see end */
8171             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8172         }
8173         serverMoves = fopen(appData.serverMovesName, "w");
8174     }
8175
8176     ExitAnalyzeMode();
8177     gameMode = BeginningOfGame;
8178     ModeHighlight();
8179     if(appData.icsActive) gameInfo.variant = VariantNormal;
8180     currentMove = forwardMostMove = backwardMostMove = 0;
8181     InitPosition(redraw);
8182     for (i = 0; i < MAX_MOVES; i++) {
8183         if (commentList[i] != NULL) {
8184             free(commentList[i]);
8185             commentList[i] = NULL;
8186         }
8187     }
8188     ResetClocks();
8189     timeRemaining[0][0] = whiteTimeRemaining;
8190     timeRemaining[1][0] = blackTimeRemaining;
8191     if (first.pr == NULL) {
8192         StartChessProgram(&first);
8193     }
8194     if (init) {
8195             InitChessProgram(&first, startedFromSetupPosition);
8196     }
8197     DisplayTitle("");
8198     DisplayMessage("", "");
8199     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8200     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8201 }
8202
8203 void
8204 AutoPlayGameLoop()
8205 {
8206     for (;;) {
8207         if (!AutoPlayOneMove())
8208           return;
8209         if (matchMode || appData.timeDelay == 0)
8210           continue;
8211         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8212           return;
8213         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8214         break;
8215     }
8216 }
8217
8218
8219 int
8220 AutoPlayOneMove()
8221 {
8222     int fromX, fromY, toX, toY;
8223
8224     if (appData.debugMode) {
8225       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8226     }
8227
8228     if (gameMode != PlayFromGameFile)
8229       return FALSE;
8230
8231     if (currentMove >= forwardMostMove) {
8232       gameMode = EditGame;
8233       ModeHighlight();
8234
8235       /* [AS] Clear current move marker at the end of a game */
8236       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8237
8238       return FALSE;
8239     }
8240     
8241     toX = moveList[currentMove][2] - AAA;
8242     toY = moveList[currentMove][3] - ONE;
8243
8244     if (moveList[currentMove][1] == '@') {
8245         if (appData.highlightLastMove) {
8246             SetHighlights(-1, -1, toX, toY);
8247         }
8248     } else {
8249         fromX = moveList[currentMove][0] - AAA;
8250         fromY = moveList[currentMove][1] - ONE;
8251
8252         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8253
8254         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8255
8256         if (appData.highlightLastMove) {
8257             SetHighlights(fromX, fromY, toX, toY);
8258         }
8259     }
8260     DisplayMove(currentMove);
8261     SendMoveToProgram(currentMove++, &first);
8262     DisplayBothClocks();
8263     DrawPosition(FALSE, boards[currentMove]);
8264     // [HGM] PV info: always display, routine tests if empty
8265     DisplayComment(currentMove - 1, commentList[currentMove]);
8266     return TRUE;
8267 }
8268
8269
8270 int
8271 LoadGameOneMove(readAhead)
8272      ChessMove readAhead;
8273 {
8274     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8275     char promoChar = NULLCHAR;
8276     ChessMove moveType;
8277     char move[MSG_SIZ];
8278     char *p, *q;
8279     
8280     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8281         gameMode != AnalyzeMode && gameMode != Training) {
8282         gameFileFP = NULL;
8283         return FALSE;
8284     }
8285     
8286     yyboardindex = forwardMostMove;
8287     if (readAhead != (ChessMove)0) {
8288       moveType = readAhead;
8289     } else {
8290       if (gameFileFP == NULL)
8291           return FALSE;
8292       moveType = (ChessMove) yylex();
8293     }
8294     
8295     done = FALSE;
8296     switch (moveType) {
8297       case Comment:
8298         if (appData.debugMode) 
8299           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8300         p = yy_text;
8301         if (*p == '{' || *p == '[' || *p == '(') {
8302             p[strlen(p) - 1] = NULLCHAR;
8303             p++;
8304         }
8305
8306         /* append the comment but don't display it */
8307         while (*p == '\n') p++;
8308         AppendComment(currentMove, p);
8309         return TRUE;
8310
8311       case WhiteCapturesEnPassant:
8312       case BlackCapturesEnPassant:
8313       case WhitePromotionChancellor:
8314       case BlackPromotionChancellor:
8315       case WhitePromotionArchbishop:
8316       case BlackPromotionArchbishop:
8317       case WhitePromotionCentaur:
8318       case BlackPromotionCentaur:
8319       case WhitePromotionQueen:
8320       case BlackPromotionQueen:
8321       case WhitePromotionRook:
8322       case BlackPromotionRook:
8323       case WhitePromotionBishop:
8324       case BlackPromotionBishop:
8325       case WhitePromotionKnight:
8326       case BlackPromotionKnight:
8327       case WhitePromotionKing:
8328       case BlackPromotionKing:
8329       case NormalMove:
8330       case WhiteKingSideCastle:
8331       case WhiteQueenSideCastle:
8332       case BlackKingSideCastle:
8333       case BlackQueenSideCastle:
8334       case WhiteKingSideCastleWild:
8335       case WhiteQueenSideCastleWild:
8336       case BlackKingSideCastleWild:
8337       case BlackQueenSideCastleWild:
8338       /* PUSH Fabien */
8339       case WhiteHSideCastleFR:
8340       case WhiteASideCastleFR:
8341       case BlackHSideCastleFR:
8342       case BlackASideCastleFR:
8343       /* POP Fabien */
8344         if (appData.debugMode)
8345           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8346         fromX = currentMoveString[0] - AAA;
8347         fromY = currentMoveString[1] - ONE;
8348         toX = currentMoveString[2] - AAA;
8349         toY = currentMoveString[3] - ONE;
8350         promoChar = currentMoveString[4];
8351         break;
8352
8353       case WhiteDrop:
8354       case BlackDrop:
8355         if (appData.debugMode)
8356           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8357         fromX = moveType == WhiteDrop ?
8358           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8359         (int) CharToPiece(ToLower(currentMoveString[0]));
8360         fromY = DROP_RANK;
8361         toX = currentMoveString[2] - AAA;
8362         toY = currentMoveString[3] - ONE;
8363         break;
8364
8365       case WhiteWins:
8366       case BlackWins:
8367       case GameIsDrawn:
8368       case GameUnfinished:
8369         if (appData.debugMode)
8370           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8371         p = strchr(yy_text, '{');
8372         if (p == NULL) p = strchr(yy_text, '(');
8373         if (p == NULL) {
8374             p = yy_text;
8375             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8376         } else {
8377             q = strchr(p, *p == '{' ? '}' : ')');
8378             if (q != NULL) *q = NULLCHAR;
8379             p++;
8380         }
8381         GameEnds(moveType, p, GE_FILE);
8382         done = TRUE;
8383         if (cmailMsgLoaded) {
8384             ClearHighlights();
8385             flipView = WhiteOnMove(currentMove);
8386             if (moveType == GameUnfinished) flipView = !flipView;
8387             if (appData.debugMode)
8388               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8389         }
8390         break;
8391
8392       case (ChessMove) 0:       /* end of file */
8393         if (appData.debugMode)
8394           fprintf(debugFP, "Parser hit end of file\n");
8395         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8396                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8397           case MT_NONE:
8398           case MT_CHECK:
8399             break;
8400           case MT_CHECKMATE:
8401           case MT_STAINMATE:
8402             if (WhiteOnMove(currentMove)) {
8403                 GameEnds(BlackWins, "Black mates", GE_FILE);
8404             } else {
8405                 GameEnds(WhiteWins, "White mates", GE_FILE);
8406             }
8407             break;
8408           case MT_STALEMATE:
8409             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8410             break;
8411         }
8412         done = TRUE;
8413         break;
8414
8415       case MoveNumberOne:
8416         if (lastLoadGameStart == GNUChessGame) {
8417             /* GNUChessGames have numbers, but they aren't move numbers */
8418             if (appData.debugMode)
8419               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8420                       yy_text, (int) moveType);
8421             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8422         }
8423         /* else fall thru */
8424
8425       case XBoardGame:
8426       case GNUChessGame:
8427       case PGNTag:
8428         /* Reached start of next game in file */
8429         if (appData.debugMode)
8430           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8431         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8432                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8433           case MT_NONE:
8434           case MT_CHECK:
8435             break;
8436           case MT_CHECKMATE:
8437           case MT_STAINMATE:
8438             if (WhiteOnMove(currentMove)) {
8439                 GameEnds(BlackWins, "Black mates", GE_FILE);
8440             } else {
8441                 GameEnds(WhiteWins, "White mates", GE_FILE);
8442             }
8443             break;
8444           case MT_STALEMATE:
8445             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8446             break;
8447         }
8448         done = TRUE;
8449         break;
8450
8451       case PositionDiagram:     /* should not happen; ignore */
8452       case ElapsedTime:         /* ignore */
8453       case NAG:                 /* ignore */
8454         if (appData.debugMode)
8455           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8456                   yy_text, (int) moveType);
8457         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8458
8459       case IllegalMove:
8460         if (appData.testLegality) {
8461             if (appData.debugMode)
8462               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8463             sprintf(move, _("Illegal move: %d.%s%s"),
8464                     (forwardMostMove / 2) + 1,
8465                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8466             DisplayError(move, 0);
8467             done = TRUE;
8468         } else {
8469             if (appData.debugMode)
8470               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8471                       yy_text, currentMoveString);
8472             fromX = currentMoveString[0] - AAA;
8473             fromY = currentMoveString[1] - ONE;
8474             toX = currentMoveString[2] - AAA;
8475             toY = currentMoveString[3] - ONE;
8476             promoChar = currentMoveString[4];
8477         }
8478         break;
8479
8480       case AmbiguousMove:
8481         if (appData.debugMode)
8482           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8483         sprintf(move, _("Ambiguous move: %d.%s%s"),
8484                 (forwardMostMove / 2) + 1,
8485                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8486         DisplayError(move, 0);
8487         done = TRUE;
8488         break;
8489
8490       default:
8491       case ImpossibleMove:
8492         if (appData.debugMode)
8493           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8494         sprintf(move, _("Illegal move: %d.%s%s"),
8495                 (forwardMostMove / 2) + 1,
8496                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8497         DisplayError(move, 0);
8498         done = TRUE;
8499         break;
8500     }
8501
8502     if (done) {
8503         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8504             DrawPosition(FALSE, boards[currentMove]);
8505             DisplayBothClocks();
8506             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8507               DisplayComment(currentMove - 1, commentList[currentMove]);
8508         }
8509         (void) StopLoadGameTimer();
8510         gameFileFP = NULL;
8511         cmailOldMove = forwardMostMove;
8512         return FALSE;
8513     } else {
8514         /* currentMoveString is set as a side-effect of yylex */
8515         strcat(currentMoveString, "\n");
8516         strcpy(moveList[forwardMostMove], currentMoveString);
8517         
8518         thinkOutput[0] = NULLCHAR;
8519         MakeMove(fromX, fromY, toX, toY, promoChar);
8520         currentMove = forwardMostMove;
8521         return TRUE;
8522     }
8523 }
8524
8525 /* Load the nth game from the given file */
8526 int
8527 LoadGameFromFile(filename, n, title, useList)
8528      char *filename;
8529      int n;
8530      char *title;
8531      /*Boolean*/ int useList;
8532 {
8533     FILE *f;
8534     char buf[MSG_SIZ];
8535
8536     if (strcmp(filename, "-") == 0) {
8537         f = stdin;
8538         title = "stdin";
8539     } else {
8540         f = fopen(filename, "rb");
8541         if (f == NULL) {
8542           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8543             DisplayError(buf, errno);
8544             return FALSE;
8545         }
8546     }
8547     if (fseek(f, 0, 0) == -1) {
8548         /* f is not seekable; probably a pipe */
8549         useList = FALSE;
8550     }
8551     if (useList && n == 0) {
8552         int error = GameListBuild(f);
8553         if (error) {
8554             DisplayError(_("Cannot build game list"), error);
8555         } else if (!ListEmpty(&gameList) &&
8556                    ((ListGame *) gameList.tailPred)->number > 1) {
8557             GameListPopUp(f, title);
8558             return TRUE;
8559         }
8560         GameListDestroy();
8561         n = 1;
8562     }
8563     if (n == 0) n = 1;
8564     return LoadGame(f, n, title, FALSE);
8565 }
8566
8567
8568 void
8569 MakeRegisteredMove()
8570 {
8571     int fromX, fromY, toX, toY;
8572     char promoChar;
8573     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8574         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8575           case CMAIL_MOVE:
8576           case CMAIL_DRAW:
8577             if (appData.debugMode)
8578               fprintf(debugFP, "Restoring %s for game %d\n",
8579                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8580     
8581             thinkOutput[0] = NULLCHAR;
8582             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8583             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8584             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8585             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8586             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8587             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8588             MakeMove(fromX, fromY, toX, toY, promoChar);
8589             ShowMove(fromX, fromY, toX, toY);
8590               
8591             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8592                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8593               case MT_NONE:
8594               case MT_CHECK:
8595                 break;
8596                 
8597               case MT_CHECKMATE:
8598               case MT_STAINMATE:
8599                 if (WhiteOnMove(currentMove)) {
8600                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8601                 } else {
8602                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8603                 }
8604                 break;
8605                 
8606               case MT_STALEMATE:
8607                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8608                 break;
8609             }
8610
8611             break;
8612             
8613           case CMAIL_RESIGN:
8614             if (WhiteOnMove(currentMove)) {
8615                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8616             } else {
8617                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8618             }
8619             break;
8620             
8621           case CMAIL_ACCEPT:
8622             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8623             break;
8624               
8625           default:
8626             break;
8627         }
8628     }
8629
8630     return;
8631 }
8632
8633 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8634 int
8635 CmailLoadGame(f, gameNumber, title, useList)
8636      FILE *f;
8637      int gameNumber;
8638      char *title;
8639      int useList;
8640 {
8641     int retVal;
8642
8643     if (gameNumber > nCmailGames) {
8644         DisplayError(_("No more games in this message"), 0);
8645         return FALSE;
8646     }
8647     if (f == lastLoadGameFP) {
8648         int offset = gameNumber - lastLoadGameNumber;
8649         if (offset == 0) {
8650             cmailMsg[0] = NULLCHAR;
8651             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8652                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8653                 nCmailMovesRegistered--;
8654             }
8655             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8656             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8657                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8658             }
8659         } else {
8660             if (! RegisterMove()) return FALSE;
8661         }
8662     }
8663
8664     retVal = LoadGame(f, gameNumber, title, useList);
8665
8666     /* Make move registered during previous look at this game, if any */
8667     MakeRegisteredMove();
8668
8669     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8670         commentList[currentMove]
8671           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8672         DisplayComment(currentMove - 1, commentList[currentMove]);
8673     }
8674
8675     return retVal;
8676 }
8677
8678 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8679 int
8680 ReloadGame(offset)
8681      int offset;
8682 {
8683     int gameNumber = lastLoadGameNumber + offset;
8684     if (lastLoadGameFP == NULL) {
8685         DisplayError(_("No game has been loaded yet"), 0);
8686         return FALSE;
8687     }
8688     if (gameNumber <= 0) {
8689         DisplayError(_("Can't back up any further"), 0);
8690         return FALSE;
8691     }
8692     if (cmailMsgLoaded) {
8693         return CmailLoadGame(lastLoadGameFP, gameNumber,
8694                              lastLoadGameTitle, lastLoadGameUseList);
8695     } else {
8696         return LoadGame(lastLoadGameFP, gameNumber,
8697                         lastLoadGameTitle, lastLoadGameUseList);
8698     }
8699 }
8700
8701
8702
8703 /* Load the nth game from open file f */
8704 int
8705 LoadGame(f, gameNumber, title, useList)
8706      FILE *f;
8707      int gameNumber;
8708      char *title;
8709      int useList;
8710 {
8711     ChessMove cm;
8712     char buf[MSG_SIZ];
8713     int gn = gameNumber;
8714     ListGame *lg = NULL;
8715     int numPGNTags = 0;
8716     int err;
8717     GameMode oldGameMode;
8718     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8719
8720     if (appData.debugMode) 
8721         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8722
8723     if (gameMode == Training )
8724         SetTrainingModeOff();
8725
8726     oldGameMode = gameMode;
8727     if (gameMode != BeginningOfGame) {
8728       Reset(FALSE, TRUE);
8729     }
8730
8731     gameFileFP = f;
8732     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8733         fclose(lastLoadGameFP);
8734     }
8735
8736     if (useList) {
8737         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8738         
8739         if (lg) {
8740             fseek(f, lg->offset, 0);
8741             GameListHighlight(gameNumber);
8742             gn = 1;
8743         }
8744         else {
8745             DisplayError(_("Game number out of range"), 0);
8746             return FALSE;
8747         }
8748     } else {
8749         GameListDestroy();
8750         if (fseek(f, 0, 0) == -1) {
8751             if (f == lastLoadGameFP ?
8752                 gameNumber == lastLoadGameNumber + 1 :
8753                 gameNumber == 1) {
8754                 gn = 1;
8755             } else {
8756                 DisplayError(_("Can't seek on game file"), 0);
8757                 return FALSE;
8758             }
8759         }
8760     }
8761     lastLoadGameFP = f;
8762     lastLoadGameNumber = gameNumber;
8763     strcpy(lastLoadGameTitle, title);
8764     lastLoadGameUseList = useList;
8765
8766     yynewfile(f);
8767
8768     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8769       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8770                 lg->gameInfo.black);
8771             DisplayTitle(buf);
8772     } else if (*title != NULLCHAR) {
8773         if (gameNumber > 1) {
8774             sprintf(buf, "%s %d", title, gameNumber);
8775             DisplayTitle(buf);
8776         } else {
8777             DisplayTitle(title);
8778         }
8779     }
8780
8781     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8782         gameMode = PlayFromGameFile;
8783         ModeHighlight();
8784     }
8785
8786     currentMove = forwardMostMove = backwardMostMove = 0;
8787     CopyBoard(boards[0], initialPosition);
8788     StopClocks();
8789
8790     /*
8791      * Skip the first gn-1 games in the file.
8792      * Also skip over anything that precedes an identifiable 
8793      * start of game marker, to avoid being confused by 
8794      * garbage at the start of the file.  Currently 
8795      * recognized start of game markers are the move number "1",
8796      * the pattern "gnuchess .* game", the pattern
8797      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8798      * A game that starts with one of the latter two patterns
8799      * will also have a move number 1, possibly
8800      * following a position diagram.
8801      * 5-4-02: Let's try being more lenient and allowing a game to
8802      * start with an unnumbered move.  Does that break anything?
8803      */
8804     cm = lastLoadGameStart = (ChessMove) 0;
8805     while (gn > 0) {
8806         yyboardindex = forwardMostMove;
8807         cm = (ChessMove) yylex();
8808         switch (cm) {
8809           case (ChessMove) 0:
8810             if (cmailMsgLoaded) {
8811                 nCmailGames = CMAIL_MAX_GAMES - gn;
8812             } else {
8813                 Reset(TRUE, TRUE);
8814                 DisplayError(_("Game not found in file"), 0);
8815             }
8816             return FALSE;
8817
8818           case GNUChessGame:
8819           case XBoardGame:
8820             gn--;
8821             lastLoadGameStart = cm;
8822             break;
8823             
8824           case MoveNumberOne:
8825             switch (lastLoadGameStart) {
8826               case GNUChessGame:
8827               case XBoardGame:
8828               case PGNTag:
8829                 break;
8830               case MoveNumberOne:
8831               case (ChessMove) 0:
8832                 gn--;           /* count this game */
8833                 lastLoadGameStart = cm;
8834                 break;
8835               default:
8836                 /* impossible */
8837                 break;
8838             }
8839             break;
8840
8841           case PGNTag:
8842             switch (lastLoadGameStart) {
8843               case GNUChessGame:
8844               case PGNTag:
8845               case MoveNumberOne:
8846               case (ChessMove) 0:
8847                 gn--;           /* count this game */
8848                 lastLoadGameStart = cm;
8849                 break;
8850               case XBoardGame:
8851                 lastLoadGameStart = cm; /* game counted already */
8852                 break;
8853               default:
8854                 /* impossible */
8855                 break;
8856             }
8857             if (gn > 0) {
8858                 do {
8859                     yyboardindex = forwardMostMove;
8860                     cm = (ChessMove) yylex();
8861                 } while (cm == PGNTag || cm == Comment);
8862             }
8863             break;
8864
8865           case WhiteWins:
8866           case BlackWins:
8867           case GameIsDrawn:
8868             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8869                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8870                     != CMAIL_OLD_RESULT) {
8871                     nCmailResults ++ ;
8872                     cmailResult[  CMAIL_MAX_GAMES
8873                                 - gn - 1] = CMAIL_OLD_RESULT;
8874                 }
8875             }
8876             break;
8877
8878           case NormalMove:
8879             /* Only a NormalMove can be at the start of a game
8880              * without a position diagram. */
8881             if (lastLoadGameStart == (ChessMove) 0) {
8882               gn--;
8883               lastLoadGameStart = MoveNumberOne;
8884             }
8885             break;
8886
8887           default:
8888             break;
8889         }
8890     }
8891     
8892     if (appData.debugMode)
8893       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8894
8895     if (cm == XBoardGame) {
8896         /* Skip any header junk before position diagram and/or move 1 */
8897         for (;;) {
8898             yyboardindex = forwardMostMove;
8899             cm = (ChessMove) yylex();
8900
8901             if (cm == (ChessMove) 0 ||
8902                 cm == GNUChessGame || cm == XBoardGame) {
8903                 /* Empty game; pretend end-of-file and handle later */
8904                 cm = (ChessMove) 0;
8905                 break;
8906             }
8907
8908             if (cm == MoveNumberOne || cm == PositionDiagram ||
8909                 cm == PGNTag || cm == Comment)
8910               break;
8911         }
8912     } else if (cm == GNUChessGame) {
8913         if (gameInfo.event != NULL) {
8914             free(gameInfo.event);
8915         }
8916         gameInfo.event = StrSave(yy_text);
8917     }   
8918
8919     startedFromSetupPosition = FALSE;
8920     while (cm == PGNTag) {
8921         if (appData.debugMode) 
8922           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8923         err = ParsePGNTag(yy_text, &gameInfo);
8924         if (!err) numPGNTags++;
8925
8926         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8927         if(gameInfo.variant != oldVariant) {
8928             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8929             InitPosition(TRUE);
8930             oldVariant = gameInfo.variant;
8931             if (appData.debugMode) 
8932               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8933         }
8934
8935
8936         if (gameInfo.fen != NULL) {
8937           Board initial_position;
8938           startedFromSetupPosition = TRUE;
8939           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8940             Reset(TRUE, TRUE);
8941             DisplayError(_("Bad FEN position in file"), 0);
8942             return FALSE;
8943           }
8944           CopyBoard(boards[0], initial_position);
8945           if (blackPlaysFirst) {
8946             currentMove = forwardMostMove = backwardMostMove = 1;
8947             CopyBoard(boards[1], initial_position);
8948             strcpy(moveList[0], "");
8949             strcpy(parseList[0], "");
8950             timeRemaining[0][1] = whiteTimeRemaining;
8951             timeRemaining[1][1] = blackTimeRemaining;
8952             if (commentList[0] != NULL) {
8953               commentList[1] = commentList[0];
8954               commentList[0] = NULL;
8955             }
8956           } else {
8957             currentMove = forwardMostMove = backwardMostMove = 0;
8958           }
8959           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8960           {   int i;
8961               initialRulePlies = FENrulePlies;
8962               epStatus[forwardMostMove] = FENepStatus;
8963               for( i=0; i< nrCastlingRights; i++ )
8964                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8965           }
8966           yyboardindex = forwardMostMove;
8967           free(gameInfo.fen);
8968           gameInfo.fen = NULL;
8969         }
8970
8971         yyboardindex = forwardMostMove;
8972         cm = (ChessMove) yylex();
8973
8974         /* Handle comments interspersed among the tags */
8975         while (cm == Comment) {
8976             char *p;
8977             if (appData.debugMode) 
8978               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8979             p = yy_text;
8980             if (*p == '{' || *p == '[' || *p == '(') {
8981                 p[strlen(p) - 1] = NULLCHAR;
8982                 p++;
8983             }
8984             while (*p == '\n') p++;
8985             AppendComment(currentMove, p);
8986             yyboardindex = forwardMostMove;
8987             cm = (ChessMove) yylex();
8988         }
8989     }
8990
8991     /* don't rely on existence of Event tag since if game was
8992      * pasted from clipboard the Event tag may not exist
8993      */
8994     if (numPGNTags > 0){
8995         char *tags;
8996         if (gameInfo.variant == VariantNormal) {
8997           gameInfo.variant = StringToVariant(gameInfo.event);
8998         }
8999         if (!matchMode) {
9000           if( appData.autoDisplayTags ) {
9001             tags = PGNTags(&gameInfo);
9002             TagsPopUp(tags, CmailMsg());
9003             free(tags);
9004           }
9005         }
9006     } else {
9007         /* Make something up, but don't display it now */
9008         SetGameInfo();
9009         TagsPopDown();
9010     }
9011
9012     if (cm == PositionDiagram) {
9013         int i, j;
9014         char *p;
9015         Board initial_position;
9016
9017         if (appData.debugMode)
9018           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9019
9020         if (!startedFromSetupPosition) {
9021             p = yy_text;
9022             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9023               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9024                 switch (*p) {
9025                   case '[':
9026                   case '-':
9027                   case ' ':
9028                   case '\t':
9029                   case '\n':
9030                   case '\r':
9031                     break;
9032                   default:
9033                     initial_position[i][j++] = CharToPiece(*p);
9034                     break;
9035                 }
9036             while (*p == ' ' || *p == '\t' ||
9037                    *p == '\n' || *p == '\r') p++;
9038         
9039             if (strncmp(p, "black", strlen("black"))==0)
9040               blackPlaysFirst = TRUE;
9041             else
9042               blackPlaysFirst = FALSE;
9043             startedFromSetupPosition = TRUE;
9044         
9045             CopyBoard(boards[0], initial_position);
9046             if (blackPlaysFirst) {
9047                 currentMove = forwardMostMove = backwardMostMove = 1;
9048                 CopyBoard(boards[1], initial_position);
9049                 strcpy(moveList[0], "");
9050                 strcpy(parseList[0], "");
9051                 timeRemaining[0][1] = whiteTimeRemaining;
9052                 timeRemaining[1][1] = blackTimeRemaining;
9053                 if (commentList[0] != NULL) {
9054                     commentList[1] = commentList[0];
9055                     commentList[0] = NULL;
9056                 }
9057             } else {
9058                 currentMove = forwardMostMove = backwardMostMove = 0;
9059             }
9060         }
9061         yyboardindex = forwardMostMove;
9062         cm = (ChessMove) yylex();
9063     }
9064
9065     if (first.pr == NoProc) {
9066         StartChessProgram(&first);
9067     }
9068     InitChessProgram(&first, FALSE);
9069     SendToProgram("force\n", &first);
9070     if (startedFromSetupPosition) {
9071         SendBoard(&first, forwardMostMove);
9072     if (appData.debugMode) {
9073         fprintf(debugFP, "Load Game\n");
9074     }
9075         DisplayBothClocks();
9076     }      
9077
9078     /* [HGM] server: flag to write setup moves in broadcast file as one */
9079     loadFlag = appData.suppressLoadMoves;
9080
9081     while (cm == Comment) {
9082         char *p;
9083         if (appData.debugMode) 
9084           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9085         p = yy_text;
9086         if (*p == '{' || *p == '[' || *p == '(') {
9087             p[strlen(p) - 1] = NULLCHAR;
9088             p++;
9089         }
9090         while (*p == '\n') p++;
9091         AppendComment(currentMove, p);
9092         yyboardindex = forwardMostMove;
9093         cm = (ChessMove) yylex();
9094     }
9095
9096     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9097         cm == WhiteWins || cm == BlackWins ||
9098         cm == GameIsDrawn || cm == GameUnfinished) {
9099         DisplayMessage("", _("No moves in game"));
9100         if (cmailMsgLoaded) {
9101             if (appData.debugMode)
9102               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9103             ClearHighlights();
9104             flipView = FALSE;
9105         }
9106         DrawPosition(FALSE, boards[currentMove]);
9107         DisplayBothClocks();
9108         gameMode = EditGame;
9109         ModeHighlight();
9110         gameFileFP = NULL;
9111         cmailOldMove = 0;
9112         return TRUE;
9113     }
9114
9115     // [HGM] PV info: routine tests if comment empty
9116     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9117         DisplayComment(currentMove - 1, commentList[currentMove]);
9118     }
9119     if (!matchMode && appData.timeDelay != 0) 
9120       DrawPosition(FALSE, boards[currentMove]);
9121
9122     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9123       programStats.ok_to_send = 1;
9124     }
9125
9126     /* if the first token after the PGN tags is a move
9127      * and not move number 1, retrieve it from the parser 
9128      */
9129     if (cm != MoveNumberOne)
9130         LoadGameOneMove(cm);
9131
9132     /* load the remaining moves from the file */
9133     while (LoadGameOneMove((ChessMove)0)) {
9134       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9135       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9136     }
9137
9138     /* rewind to the start of the game */
9139     currentMove = backwardMostMove;
9140
9141     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9142
9143     if (oldGameMode == AnalyzeFile ||
9144         oldGameMode == AnalyzeMode) {
9145       AnalyzeFileEvent();
9146     }
9147
9148     if (matchMode || appData.timeDelay == 0) {
9149       ToEndEvent();
9150       gameMode = EditGame;
9151       ModeHighlight();
9152     } else if (appData.timeDelay > 0) {
9153       AutoPlayGameLoop();
9154     }
9155
9156     if (appData.debugMode) 
9157         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9158
9159     loadFlag = 0; /* [HGM] true game starts */
9160     return TRUE;
9161 }
9162
9163 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9164 int
9165 ReloadPosition(offset)
9166      int offset;
9167 {
9168     int positionNumber = lastLoadPositionNumber + offset;
9169     if (lastLoadPositionFP == NULL) {
9170         DisplayError(_("No position has been loaded yet"), 0);
9171         return FALSE;
9172     }
9173     if (positionNumber <= 0) {
9174         DisplayError(_("Can't back up any further"), 0);
9175         return FALSE;
9176     }
9177     return LoadPosition(lastLoadPositionFP, positionNumber,
9178                         lastLoadPositionTitle);
9179 }
9180
9181 /* Load the nth position from the given file */
9182 int
9183 LoadPositionFromFile(filename, n, title)
9184      char *filename;
9185      int n;
9186      char *title;
9187 {
9188     FILE *f;
9189     char buf[MSG_SIZ];
9190
9191     if (strcmp(filename, "-") == 0) {
9192         return LoadPosition(stdin, n, "stdin");
9193     } else {
9194         f = fopen(filename, "rb");
9195         if (f == NULL) {
9196             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9197             DisplayError(buf, errno);
9198             return FALSE;
9199         } else {
9200             return LoadPosition(f, n, title);
9201         }
9202     }
9203 }
9204
9205 /* Load the nth position from the given open file, and close it */
9206 int
9207 LoadPosition(f, positionNumber, title)
9208      FILE *f;
9209      int positionNumber;
9210      char *title;
9211 {
9212     char *p, line[MSG_SIZ];
9213     Board initial_position;
9214     int i, j, fenMode, pn;
9215     
9216     if (gameMode == Training )
9217         SetTrainingModeOff();
9218
9219     if (gameMode != BeginningOfGame) {
9220         Reset(FALSE, TRUE);
9221     }
9222     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9223         fclose(lastLoadPositionFP);
9224     }
9225     if (positionNumber == 0) positionNumber = 1;
9226     lastLoadPositionFP = f;
9227     lastLoadPositionNumber = positionNumber;
9228     strcpy(lastLoadPositionTitle, title);
9229     if (first.pr == NoProc) {
9230       StartChessProgram(&first);
9231       InitChessProgram(&first, FALSE);
9232     }    
9233     pn = positionNumber;
9234     if (positionNumber < 0) {
9235         /* Negative position number means to seek to that byte offset */
9236         if (fseek(f, -positionNumber, 0) == -1) {
9237             DisplayError(_("Can't seek on position file"), 0);
9238             return FALSE;
9239         };
9240         pn = 1;
9241     } else {
9242         if (fseek(f, 0, 0) == -1) {
9243             if (f == lastLoadPositionFP ?
9244                 positionNumber == lastLoadPositionNumber + 1 :
9245                 positionNumber == 1) {
9246                 pn = 1;
9247             } else {
9248                 DisplayError(_("Can't seek on position file"), 0);
9249                 return FALSE;
9250             }
9251         }
9252     }
9253     /* See if this file is FEN or old-style xboard */
9254     if (fgets(line, MSG_SIZ, f) == NULL) {
9255         DisplayError(_("Position not found in file"), 0);
9256         return FALSE;
9257     }
9258     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9259     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9260
9261     if (pn >= 2) {
9262         if (fenMode || line[0] == '#') pn--;
9263         while (pn > 0) {
9264             /* skip positions before number pn */
9265             if (fgets(line, MSG_SIZ, f) == NULL) {
9266                 Reset(TRUE, TRUE);
9267                 DisplayError(_("Position not found in file"), 0);
9268                 return FALSE;
9269             }
9270             if (fenMode || line[0] == '#') pn--;
9271         }
9272     }
9273
9274     if (fenMode) {
9275         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9276             DisplayError(_("Bad FEN position in file"), 0);
9277             return FALSE;
9278         }
9279     } else {
9280         (void) fgets(line, MSG_SIZ, f);
9281         (void) fgets(line, MSG_SIZ, f);
9282     
9283         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9284             (void) fgets(line, MSG_SIZ, f);
9285             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9286                 if (*p == ' ')
9287                   continue;
9288                 initial_position[i][j++] = CharToPiece(*p);
9289             }
9290         }
9291     
9292         blackPlaysFirst = FALSE;
9293         if (!feof(f)) {
9294             (void) fgets(line, MSG_SIZ, f);
9295             if (strncmp(line, "black", strlen("black"))==0)
9296               blackPlaysFirst = TRUE;
9297         }
9298     }
9299     startedFromSetupPosition = TRUE;
9300     
9301     SendToProgram("force\n", &first);
9302     CopyBoard(boards[0], initial_position);
9303     if (blackPlaysFirst) {
9304         currentMove = forwardMostMove = backwardMostMove = 1;
9305         strcpy(moveList[0], "");
9306         strcpy(parseList[0], "");
9307         CopyBoard(boards[1], initial_position);
9308         DisplayMessage("", _("Black to play"));
9309     } else {
9310         currentMove = forwardMostMove = backwardMostMove = 0;
9311         DisplayMessage("", _("White to play"));
9312     }
9313           /* [HGM] copy FEN attributes as well */
9314           {   int i;
9315               initialRulePlies = FENrulePlies;
9316               epStatus[forwardMostMove] = FENepStatus;
9317               for( i=0; i< nrCastlingRights; i++ )
9318                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9319           }
9320     SendBoard(&first, forwardMostMove);
9321     if (appData.debugMode) {
9322 int i, j;
9323   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9324   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9325         fprintf(debugFP, "Load Position\n");
9326     }
9327
9328     if (positionNumber > 1) {
9329         sprintf(line, "%s %d", title, positionNumber);
9330         DisplayTitle(line);
9331     } else {
9332         DisplayTitle(title);
9333     }
9334     gameMode = EditGame;
9335     ModeHighlight();
9336     ResetClocks();
9337     timeRemaining[0][1] = whiteTimeRemaining;
9338     timeRemaining[1][1] = blackTimeRemaining;
9339     DrawPosition(FALSE, boards[currentMove]);
9340    
9341     return TRUE;
9342 }
9343
9344
9345 void
9346 CopyPlayerNameIntoFileName(dest, src)
9347      char **dest, *src;
9348 {
9349     while (*src != NULLCHAR && *src != ',') {
9350         if (*src == ' ') {
9351             *(*dest)++ = '_';
9352             src++;
9353         } else {
9354             *(*dest)++ = *src++;
9355         }
9356     }
9357 }
9358
9359 char *DefaultFileName(ext)
9360      char *ext;
9361 {
9362     static char def[MSG_SIZ];
9363     char *p;
9364
9365     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9366         p = def;
9367         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9368         *p++ = '-';
9369         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9370         *p++ = '.';
9371         strcpy(p, ext);
9372     } else {
9373         def[0] = NULLCHAR;
9374     }
9375     return def;
9376 }
9377
9378 /* Save the current game to the given file */
9379 int
9380 SaveGameToFile(filename, append)
9381      char *filename;
9382      int append;
9383 {
9384     FILE *f;
9385     char buf[MSG_SIZ];
9386
9387     if (strcmp(filename, "-") == 0) {
9388         return SaveGame(stdout, 0, NULL);
9389     } else {
9390         f = fopen(filename, append ? "a" : "w");
9391         if (f == NULL) {
9392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9393             DisplayError(buf, errno);
9394             return FALSE;
9395         } else {
9396             return SaveGame(f, 0, NULL);
9397         }
9398     }
9399 }
9400
9401 char *
9402 SavePart(str)
9403      char *str;
9404 {
9405     static char buf[MSG_SIZ];
9406     char *p;
9407     
9408     p = strchr(str, ' ');
9409     if (p == NULL) return str;
9410     strncpy(buf, str, p - str);
9411     buf[p - str] = NULLCHAR;
9412     return buf;
9413 }
9414
9415 #define PGN_MAX_LINE 75
9416
9417 #define PGN_SIDE_WHITE  0
9418 #define PGN_SIDE_BLACK  1
9419
9420 /* [AS] */
9421 static int FindFirstMoveOutOfBook( int side )
9422 {
9423     int result = -1;
9424
9425     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9426         int index = backwardMostMove;
9427         int has_book_hit = 0;
9428
9429         if( (index % 2) != side ) {
9430             index++;
9431         }
9432
9433         while( index < forwardMostMove ) {
9434             /* Check to see if engine is in book */
9435             int depth = pvInfoList[index].depth;
9436             int score = pvInfoList[index].score;
9437             int in_book = 0;
9438
9439             if( depth <= 2 ) {
9440                 in_book = 1;
9441             }
9442             else if( score == 0 && depth == 63 ) {
9443                 in_book = 1; /* Zappa */
9444             }
9445             else if( score == 2 && depth == 99 ) {
9446                 in_book = 1; /* Abrok */
9447             }
9448
9449             has_book_hit += in_book;
9450
9451             if( ! in_book ) {
9452                 result = index;
9453
9454                 break;
9455             }
9456
9457             index += 2;
9458         }
9459     }
9460
9461     return result;
9462 }
9463
9464 /* [AS] */
9465 void GetOutOfBookInfo( char * buf )
9466 {
9467     int oob[2];
9468     int i;
9469     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9470
9471     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9472     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9473
9474     *buf = '\0';
9475
9476     if( oob[0] >= 0 || oob[1] >= 0 ) {
9477         for( i=0; i<2; i++ ) {
9478             int idx = oob[i];
9479
9480             if( idx >= 0 ) {
9481                 if( i > 0 && oob[0] >= 0 ) {
9482                     strcat( buf, "   " );
9483                 }
9484
9485                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9486                 sprintf( buf+strlen(buf), "%s%.2f", 
9487                     pvInfoList[idx].score >= 0 ? "+" : "",
9488                     pvInfoList[idx].score / 100.0 );
9489             }
9490         }
9491     }
9492 }
9493
9494 /* Save game in PGN style and close the file */
9495 int
9496 SaveGamePGN(f)
9497      FILE *f;
9498 {
9499     int i, offset, linelen, newblock;
9500     time_t tm;
9501 //    char *movetext;
9502     char numtext[32];
9503     int movelen, numlen, blank;
9504     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9505
9506     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9507     
9508     tm = time((time_t *) NULL);
9509     
9510     PrintPGNTags(f, &gameInfo);
9511     
9512     if (backwardMostMove > 0 || startedFromSetupPosition) {
9513         char *fen = PositionToFEN(backwardMostMove, NULL);
9514         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9515         fprintf(f, "\n{--------------\n");
9516         PrintPosition(f, backwardMostMove);
9517         fprintf(f, "--------------}\n");
9518         free(fen);
9519     }
9520     else {
9521         /* [AS] Out of book annotation */
9522         if( appData.saveOutOfBookInfo ) {
9523             char buf[64];
9524
9525             GetOutOfBookInfo( buf );
9526
9527             if( buf[0] != '\0' ) {
9528                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9529             }
9530         }
9531
9532         fprintf(f, "\n");
9533     }
9534
9535     i = backwardMostMove;
9536     linelen = 0;
9537     newblock = TRUE;
9538
9539     while (i < forwardMostMove) {
9540         /* Print comments preceding this move */
9541         if (commentList[i] != NULL) {
9542             if (linelen > 0) fprintf(f, "\n");
9543             fprintf(f, "{\n%s}\n", commentList[i]);
9544             linelen = 0;
9545             newblock = TRUE;
9546         }
9547
9548         /* Format move number */
9549         if ((i % 2) == 0) {
9550             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9551         } else {
9552             if (newblock) {
9553                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9554             } else {
9555                 numtext[0] = NULLCHAR;
9556             }
9557         }
9558         numlen = strlen(numtext);
9559         newblock = FALSE;
9560
9561         /* Print move number */
9562         blank = linelen > 0 && numlen > 0;
9563         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9564             fprintf(f, "\n");
9565             linelen = 0;
9566             blank = 0;
9567         }
9568         if (blank) {
9569             fprintf(f, " ");
9570             linelen++;
9571         }
9572         fprintf(f, numtext);
9573         linelen += numlen;
9574
9575         /* Get move */
9576         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9577         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9578
9579         /* Print move */
9580         blank = linelen > 0 && movelen > 0;
9581         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9582             fprintf(f, "\n");
9583             linelen = 0;
9584             blank = 0;
9585         }
9586         if (blank) {
9587             fprintf(f, " ");
9588             linelen++;
9589         }
9590         fprintf(f, move_buffer);
9591         linelen += movelen;
9592
9593         /* [AS] Add PV info if present */
9594         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9595             /* [HGM] add time */
9596             char buf[MSG_SIZ]; int seconds = 0;
9597
9598 #if 1
9599             if(i >= backwardMostMove) {
9600                 if(WhiteOnMove(i))
9601                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9602                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9603                 else
9604                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9605                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9606             }
9607             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9608 #else
9609             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9610 #endif
9611
9612             if( seconds <= 0) buf[0] = 0; else
9613             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9614                 seconds = (seconds + 4)/10; // round to full seconds
9615                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9616                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9617             }
9618
9619             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9620                 pvInfoList[i].score >= 0 ? "+" : "",
9621                 pvInfoList[i].score / 100.0,
9622                 pvInfoList[i].depth,
9623                 buf );
9624
9625             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9626
9627             /* Print score/depth */
9628             blank = linelen > 0 && movelen > 0;
9629             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9630                 fprintf(f, "\n");
9631                 linelen = 0;
9632                 blank = 0;
9633             }
9634             if (blank) {
9635                 fprintf(f, " ");
9636                 linelen++;
9637             }
9638             fprintf(f, move_buffer);
9639             linelen += movelen;
9640         }
9641
9642         i++;
9643     }
9644     
9645     /* Start a new line */
9646     if (linelen > 0) fprintf(f, "\n");
9647
9648     /* Print comments after last move */
9649     if (commentList[i] != NULL) {
9650         fprintf(f, "{\n%s}\n", commentList[i]);
9651     }
9652
9653     /* Print result */
9654     if (gameInfo.resultDetails != NULL &&
9655         gameInfo.resultDetails[0] != NULLCHAR) {
9656         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9657                 PGNResult(gameInfo.result));
9658     } else {
9659         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9660     }
9661
9662     fclose(f);
9663     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9664     return TRUE;
9665 }
9666
9667 /* Save game in old style and close the file */
9668 int
9669 SaveGameOldStyle(f)
9670      FILE *f;
9671 {
9672     int i, offset;
9673     time_t tm;
9674     
9675     tm = time((time_t *) NULL);
9676     
9677     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9678     PrintOpponents(f);
9679     
9680     if (backwardMostMove > 0 || startedFromSetupPosition) {
9681         fprintf(f, "\n[--------------\n");
9682         PrintPosition(f, backwardMostMove);
9683         fprintf(f, "--------------]\n");
9684     } else {
9685         fprintf(f, "\n");
9686     }
9687
9688     i = backwardMostMove;
9689     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9690
9691     while (i < forwardMostMove) {
9692         if (commentList[i] != NULL) {
9693             fprintf(f, "[%s]\n", commentList[i]);
9694         }
9695
9696         if ((i % 2) == 1) {
9697             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9698             i++;
9699         } else {
9700             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9701             i++;
9702             if (commentList[i] != NULL) {
9703                 fprintf(f, "\n");
9704                 continue;
9705             }
9706             if (i >= forwardMostMove) {
9707                 fprintf(f, "\n");
9708                 break;
9709             }
9710             fprintf(f, "%s\n", parseList[i]);
9711             i++;
9712         }
9713     }
9714     
9715     if (commentList[i] != NULL) {
9716         fprintf(f, "[%s]\n", commentList[i]);
9717     }
9718
9719     /* This isn't really the old style, but it's close enough */
9720     if (gameInfo.resultDetails != NULL &&
9721         gameInfo.resultDetails[0] != NULLCHAR) {
9722         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9723                 gameInfo.resultDetails);
9724     } else {
9725         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9726     }
9727
9728     fclose(f);
9729     return TRUE;
9730 }
9731
9732 /* Save the current game to open file f and close the file */
9733 int
9734 SaveGame(f, dummy, dummy2)
9735      FILE *f;
9736      int dummy;
9737      char *dummy2;
9738 {
9739     if (gameMode == EditPosition) EditPositionDone();
9740     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9741     if (appData.oldSaveStyle)
9742       return SaveGameOldStyle(f);
9743     else
9744       return SaveGamePGN(f);
9745 }
9746
9747 /* Save the current position to the given file */
9748 int
9749 SavePositionToFile(filename)
9750      char *filename;
9751 {
9752     FILE *f;
9753     char buf[MSG_SIZ];
9754
9755     if (strcmp(filename, "-") == 0) {
9756         return SavePosition(stdout, 0, NULL);
9757     } else {
9758         f = fopen(filename, "a");
9759         if (f == NULL) {
9760             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9761             DisplayError(buf, errno);
9762             return FALSE;
9763         } else {
9764             SavePosition(f, 0, NULL);
9765             return TRUE;
9766         }
9767     }
9768 }
9769
9770 /* Save the current position to the given open file and close the file */
9771 int
9772 SavePosition(f, dummy, dummy2)
9773      FILE *f;
9774      int dummy;
9775      char *dummy2;
9776 {
9777     time_t tm;
9778     char *fen;
9779     
9780     if (appData.oldSaveStyle) {
9781         tm = time((time_t *) NULL);
9782     
9783         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9784         PrintOpponents(f);
9785         fprintf(f, "[--------------\n");
9786         PrintPosition(f, currentMove);
9787         fprintf(f, "--------------]\n");
9788     } else {
9789         fen = PositionToFEN(currentMove, NULL);
9790         fprintf(f, "%s\n", fen);
9791         free(fen);
9792     }
9793     fclose(f);
9794     return TRUE;
9795 }
9796
9797 void
9798 ReloadCmailMsgEvent(unregister)
9799      int unregister;
9800 {
9801 #if !WIN32
9802     static char *inFilename = NULL;
9803     static char *outFilename;
9804     int i;
9805     struct stat inbuf, outbuf;
9806     int status;
9807     
9808     /* Any registered moves are unregistered if unregister is set, */
9809     /* i.e. invoked by the signal handler */
9810     if (unregister) {
9811         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9812             cmailMoveRegistered[i] = FALSE;
9813             if (cmailCommentList[i] != NULL) {
9814                 free(cmailCommentList[i]);
9815                 cmailCommentList[i] = NULL;
9816             }
9817         }
9818         nCmailMovesRegistered = 0;
9819     }
9820
9821     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9822         cmailResult[i] = CMAIL_NOT_RESULT;
9823     }
9824     nCmailResults = 0;
9825
9826     if (inFilename == NULL) {
9827         /* Because the filenames are static they only get malloced once  */
9828         /* and they never get freed                                      */
9829         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9830         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9831
9832         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9833         sprintf(outFilename, "%s.out", appData.cmailGameName);
9834     }
9835     
9836     status = stat(outFilename, &outbuf);
9837     if (status < 0) {
9838         cmailMailedMove = FALSE;
9839     } else {
9840         status = stat(inFilename, &inbuf);
9841         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9842     }
9843     
9844     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9845        counts the games, notes how each one terminated, etc.
9846        
9847        It would be nice to remove this kludge and instead gather all
9848        the information while building the game list.  (And to keep it
9849        in the game list nodes instead of having a bunch of fixed-size
9850        parallel arrays.)  Note this will require getting each game's
9851        termination from the PGN tags, as the game list builder does
9852        not process the game moves.  --mann
9853        */
9854     cmailMsgLoaded = TRUE;
9855     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9856     
9857     /* Load first game in the file or popup game menu */
9858     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9859
9860 #endif /* !WIN32 */
9861     return;
9862 }
9863
9864 int
9865 RegisterMove()
9866 {
9867     FILE *f;
9868     char string[MSG_SIZ];
9869
9870     if (   cmailMailedMove
9871         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9872         return TRUE;            /* Allow free viewing  */
9873     }
9874
9875     /* Unregister move to ensure that we don't leave RegisterMove        */
9876     /* with the move registered when the conditions for registering no   */
9877     /* longer hold                                                       */
9878     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9879         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9880         nCmailMovesRegistered --;
9881
9882         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9883           {
9884               free(cmailCommentList[lastLoadGameNumber - 1]);
9885               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9886           }
9887     }
9888
9889     if (cmailOldMove == -1) {
9890         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9891         return FALSE;
9892     }
9893
9894     if (currentMove > cmailOldMove + 1) {
9895         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9896         return FALSE;
9897     }
9898
9899     if (currentMove < cmailOldMove) {
9900         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9901         return FALSE;
9902     }
9903
9904     if (forwardMostMove > currentMove) {
9905         /* Silently truncate extra moves */
9906         TruncateGame();
9907     }
9908
9909     if (   (currentMove == cmailOldMove + 1)
9910         || (   (currentMove == cmailOldMove)
9911             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9912                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9913         if (gameInfo.result != GameUnfinished) {
9914             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9915         }
9916
9917         if (commentList[currentMove] != NULL) {
9918             cmailCommentList[lastLoadGameNumber - 1]
9919               = StrSave(commentList[currentMove]);
9920         }
9921         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9922
9923         if (appData.debugMode)
9924           fprintf(debugFP, "Saving %s for game %d\n",
9925                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9926
9927         sprintf(string,
9928                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9929         
9930         f = fopen(string, "w");
9931         if (appData.oldSaveStyle) {
9932             SaveGameOldStyle(f); /* also closes the file */
9933             
9934             sprintf(string, "%s.pos.out", appData.cmailGameName);
9935             f = fopen(string, "w");
9936             SavePosition(f, 0, NULL); /* also closes the file */
9937         } else {
9938             fprintf(f, "{--------------\n");
9939             PrintPosition(f, currentMove);
9940             fprintf(f, "--------------}\n\n");
9941             
9942             SaveGame(f, 0, NULL); /* also closes the file*/
9943         }
9944         
9945         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9946         nCmailMovesRegistered ++;
9947     } else if (nCmailGames == 1) {
9948         DisplayError(_("You have not made a move yet"), 0);
9949         return FALSE;
9950     }
9951
9952     return TRUE;
9953 }
9954
9955 void
9956 MailMoveEvent()
9957 {
9958 #if !WIN32
9959     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9960     FILE *commandOutput;
9961     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9962     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9963     int nBuffers;
9964     int i;
9965     int archived;
9966     char *arcDir;
9967
9968     if (! cmailMsgLoaded) {
9969         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9970         return;
9971     }
9972
9973     if (nCmailGames == nCmailResults) {
9974         DisplayError(_("No unfinished games"), 0);
9975         return;
9976     }
9977
9978 #if CMAIL_PROHIBIT_REMAIL
9979     if (cmailMailedMove) {
9980         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);
9981         DisplayError(msg, 0);
9982         return;
9983     }
9984 #endif
9985
9986     if (! (cmailMailedMove || RegisterMove())) return;
9987     
9988     if (   cmailMailedMove
9989         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
9990         sprintf(string, partCommandString,
9991                 appData.debugMode ? " -v" : "", appData.cmailGameName);
9992         commandOutput = popen(string, "r");
9993
9994         if (commandOutput == NULL) {
9995             DisplayError(_("Failed to invoke cmail"), 0);
9996         } else {
9997             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
9998                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
9999             }
10000             if (nBuffers > 1) {
10001                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10002                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10003                 nBytes = MSG_SIZ - 1;
10004             } else {
10005                 (void) memcpy(msg, buffer, nBytes);
10006             }
10007             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10008
10009             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10010                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10011
10012                 archived = TRUE;
10013                 for (i = 0; i < nCmailGames; i ++) {
10014                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10015                         archived = FALSE;
10016                     }
10017                 }
10018                 if (   archived
10019                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10020                         != NULL)) {
10021                     sprintf(buffer, "%s/%s.%s.archive",
10022                             arcDir,
10023                             appData.cmailGameName,
10024                             gameInfo.date);
10025                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10026                     cmailMsgLoaded = FALSE;
10027                 }
10028             }
10029
10030             DisplayInformation(msg);
10031             pclose(commandOutput);
10032         }
10033     } else {
10034         if ((*cmailMsg) != '\0') {
10035             DisplayInformation(cmailMsg);
10036         }
10037     }
10038
10039     return;
10040 #endif /* !WIN32 */
10041 }
10042
10043 char *
10044 CmailMsg()
10045 {
10046 #if WIN32
10047     return NULL;
10048 #else
10049     int  prependComma = 0;
10050     char number[5];
10051     char string[MSG_SIZ];       /* Space for game-list */
10052     int  i;
10053     
10054     if (!cmailMsgLoaded) return "";
10055
10056     if (cmailMailedMove) {
10057         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10058     } else {
10059         /* Create a list of games left */
10060         sprintf(string, "[");
10061         for (i = 0; i < nCmailGames; i ++) {
10062             if (! (   cmailMoveRegistered[i]
10063                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10064                 if (prependComma) {
10065                     sprintf(number, ",%d", i + 1);
10066                 } else {
10067                     sprintf(number, "%d", i + 1);
10068                     prependComma = 1;
10069                 }
10070                 
10071                 strcat(string, number);
10072             }
10073         }
10074         strcat(string, "]");
10075
10076         if (nCmailMovesRegistered + nCmailResults == 0) {
10077             switch (nCmailGames) {
10078               case 1:
10079                 sprintf(cmailMsg,
10080                         _("Still need to make move for game\n"));
10081                 break;
10082                 
10083               case 2:
10084                 sprintf(cmailMsg,
10085                         _("Still need to make moves for both games\n"));
10086                 break;
10087                 
10088               default:
10089                 sprintf(cmailMsg,
10090                         _("Still need to make moves for all %d games\n"),
10091                         nCmailGames);
10092                 break;
10093             }
10094         } else {
10095             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10096               case 1:
10097                 sprintf(cmailMsg,
10098                         _("Still need to make a move for game %s\n"),
10099                         string);
10100                 break;
10101                 
10102               case 0:
10103                 if (nCmailResults == nCmailGames) {
10104                     sprintf(cmailMsg, _("No unfinished games\n"));
10105                 } else {
10106                     sprintf(cmailMsg, _("Ready to send mail\n"));
10107                 }
10108                 break;
10109                 
10110               default:
10111                 sprintf(cmailMsg,
10112                         _("Still need to make moves for games %s\n"),
10113                         string);
10114             }
10115         }
10116     }
10117     return cmailMsg;
10118 #endif /* WIN32 */
10119 }
10120
10121 void
10122 ResetGameEvent()
10123 {
10124     if (gameMode == Training)
10125       SetTrainingModeOff();
10126
10127     Reset(TRUE, TRUE);
10128     cmailMsgLoaded = FALSE;
10129     if (appData.icsActive) {
10130       SendToICS(ics_prefix);
10131       SendToICS("refresh\n");
10132     }
10133 }
10134
10135 void
10136 ExitEvent(status)
10137      int status;
10138 {
10139     exiting++;
10140     if (exiting > 2) {
10141       /* Give up on clean exit */
10142       exit(status);
10143     }
10144     if (exiting > 1) {
10145       /* Keep trying for clean exit */
10146       return;
10147     }
10148
10149     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10150
10151     if (telnetISR != NULL) {
10152       RemoveInputSource(telnetISR);
10153     }
10154     if (icsPR != NoProc) {
10155       DestroyChildProcess(icsPR, TRUE);
10156     }
10157
10158     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10159     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10160
10161     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10162     /* make sure this other one finishes before killing it!                  */
10163     if(endingGame) { int count = 0;
10164         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10165         while(endingGame && count++ < 10) DoSleep(1);
10166         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10167     }
10168
10169     /* Kill off chess programs */
10170     if (first.pr != NoProc) {
10171         ExitAnalyzeMode();
10172         
10173         DoSleep( appData.delayBeforeQuit );
10174         SendToProgram("quit\n", &first);
10175         DoSleep( appData.delayAfterQuit );
10176         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10177     }
10178     if (second.pr != NoProc) {
10179         DoSleep( appData.delayBeforeQuit );
10180         SendToProgram("quit\n", &second);
10181         DoSleep( appData.delayAfterQuit );
10182         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10183     }
10184     if (first.isr != NULL) {
10185         RemoveInputSource(first.isr);
10186     }
10187     if (second.isr != NULL) {
10188         RemoveInputSource(second.isr);
10189     }
10190
10191     ShutDownFrontEnd();
10192     exit(status);
10193 }
10194
10195 void
10196 PauseEvent()
10197 {
10198     if (appData.debugMode)
10199         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10200     if (pausing) {
10201         pausing = FALSE;
10202         ModeHighlight();
10203         if (gameMode == MachinePlaysWhite ||
10204             gameMode == MachinePlaysBlack) {
10205             StartClocks();
10206         } else {
10207             DisplayBothClocks();
10208         }
10209         if (gameMode == PlayFromGameFile) {
10210             if (appData.timeDelay >= 0) 
10211                 AutoPlayGameLoop();
10212         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10213             Reset(FALSE, TRUE);
10214             SendToICS(ics_prefix);
10215             SendToICS("refresh\n");
10216         } else if (currentMove < forwardMostMove) {
10217             ForwardInner(forwardMostMove);
10218         }
10219         pauseExamInvalid = FALSE;
10220     } else {
10221         switch (gameMode) {
10222           default:
10223             return;
10224           case IcsExamining:
10225             pauseExamForwardMostMove = forwardMostMove;
10226             pauseExamInvalid = FALSE;
10227             /* fall through */
10228           case IcsObserving:
10229           case IcsPlayingWhite:
10230           case IcsPlayingBlack:
10231             pausing = TRUE;
10232             ModeHighlight();
10233             return;
10234           case PlayFromGameFile:
10235             (void) StopLoadGameTimer();
10236             pausing = TRUE;
10237             ModeHighlight();
10238             break;
10239           case BeginningOfGame:
10240             if (appData.icsActive) return;
10241             /* else fall through */
10242           case MachinePlaysWhite:
10243           case MachinePlaysBlack:
10244           case TwoMachinesPlay:
10245             if (forwardMostMove == 0)
10246               return;           /* don't pause if no one has moved */
10247             if ((gameMode == MachinePlaysWhite &&
10248                  !WhiteOnMove(forwardMostMove)) ||
10249                 (gameMode == MachinePlaysBlack &&
10250                  WhiteOnMove(forwardMostMove))) {
10251                 StopClocks();
10252             }
10253             pausing = TRUE;
10254             ModeHighlight();
10255             break;
10256         }
10257     }
10258 }
10259
10260 void
10261 EditCommentEvent()
10262 {
10263     char title[MSG_SIZ];
10264
10265     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10266         strcpy(title, _("Edit comment"));
10267     } else {
10268         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10269                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10270                 parseList[currentMove - 1]);
10271     }
10272
10273     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10274 }
10275
10276
10277 void
10278 EditTagsEvent()
10279 {
10280     char *tags = PGNTags(&gameInfo);
10281     EditTagsPopUp(tags);
10282     free(tags);
10283 }
10284
10285 void
10286 AnalyzeModeEvent()
10287 {
10288     if (appData.noChessProgram || gameMode == AnalyzeMode)
10289       return;
10290
10291     if (gameMode != AnalyzeFile) {
10292         if (!appData.icsEngineAnalyze) {
10293                EditGameEvent();
10294                if (gameMode != EditGame) return;
10295         }
10296         ResurrectChessProgram();
10297         SendToProgram("analyze\n", &first);
10298         first.analyzing = TRUE;
10299         /*first.maybeThinking = TRUE;*/
10300         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10301         AnalysisPopUp(_("Analysis"),
10302                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10303     }
10304     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10305     pausing = FALSE;
10306     ModeHighlight();
10307     SetGameInfo();
10308
10309     StartAnalysisClock();
10310     GetTimeMark(&lastNodeCountTime);
10311     lastNodeCount = 0;
10312 }
10313
10314 void
10315 AnalyzeFileEvent()
10316 {
10317     if (appData.noChessProgram || gameMode == AnalyzeFile)
10318       return;
10319
10320     if (gameMode != AnalyzeMode) {
10321         EditGameEvent();
10322         if (gameMode != EditGame) return;
10323         ResurrectChessProgram();
10324         SendToProgram("analyze\n", &first);
10325         first.analyzing = TRUE;
10326         /*first.maybeThinking = TRUE;*/
10327         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10328         AnalysisPopUp(_("Analysis"),
10329                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10330     }
10331     gameMode = AnalyzeFile;
10332     pausing = FALSE;
10333     ModeHighlight();
10334     SetGameInfo();
10335
10336     StartAnalysisClock();
10337     GetTimeMark(&lastNodeCountTime);
10338     lastNodeCount = 0;
10339 }
10340
10341 void
10342 MachineWhiteEvent()
10343 {
10344     char buf[MSG_SIZ];
10345     char *bookHit = NULL;
10346
10347     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10348       return;
10349
10350
10351     if (gameMode == PlayFromGameFile || 
10352         gameMode == TwoMachinesPlay  || 
10353         gameMode == Training         || 
10354         gameMode == AnalyzeMode      || 
10355         gameMode == EndOfGame)
10356         EditGameEvent();
10357
10358     if (gameMode == EditPosition) 
10359         EditPositionDone();
10360
10361     if (!WhiteOnMove(currentMove)) {
10362         DisplayError(_("It is not White's turn"), 0);
10363         return;
10364     }
10365   
10366     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10367       ExitAnalyzeMode();
10368
10369     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10370         gameMode == AnalyzeFile)
10371         TruncateGame();
10372
10373     ResurrectChessProgram();    /* in case it isn't running */
10374     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10375         gameMode = MachinePlaysWhite;
10376         ResetClocks();
10377     } else
10378     gameMode = MachinePlaysWhite;
10379     pausing = FALSE;
10380     ModeHighlight();
10381     SetGameInfo();
10382     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10383     DisplayTitle(buf);
10384     if (first.sendName) {
10385       sprintf(buf, "name %s\n", gameInfo.black);
10386       SendToProgram(buf, &first);
10387     }
10388     if (first.sendTime) {
10389       if (first.useColors) {
10390         SendToProgram("black\n", &first); /*gnu kludge*/
10391       }
10392       SendTimeRemaining(&first, TRUE);
10393     }
10394     if (first.useColors) {
10395       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10396     }
10397     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10398     SetMachineThinkingEnables();
10399     first.maybeThinking = TRUE;
10400     StartClocks();
10401     firstMove = FALSE;
10402
10403     if (appData.autoFlipView && !flipView) {
10404       flipView = !flipView;
10405       DrawPosition(FALSE, NULL);
10406       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10407     }
10408
10409     if(bookHit) { // [HGM] book: simulate book reply
10410         static char bookMove[MSG_SIZ]; // a bit generous?
10411
10412         programStats.nodes = programStats.depth = programStats.time = 
10413         programStats.score = programStats.got_only_move = 0;
10414         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10415
10416         strcpy(bookMove, "move ");
10417         strcat(bookMove, bookHit);
10418         HandleMachineMove(bookMove, &first);
10419     }
10420 }
10421
10422 void
10423 MachineBlackEvent()
10424 {
10425     char buf[MSG_SIZ];
10426    char *bookHit = NULL;
10427
10428     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10429         return;
10430
10431
10432     if (gameMode == PlayFromGameFile || 
10433         gameMode == TwoMachinesPlay  || 
10434         gameMode == Training         || 
10435         gameMode == AnalyzeMode      || 
10436         gameMode == EndOfGame)
10437         EditGameEvent();
10438
10439     if (gameMode == EditPosition) 
10440         EditPositionDone();
10441
10442     if (WhiteOnMove(currentMove)) {
10443         DisplayError(_("It is not Black's turn"), 0);
10444         return;
10445     }
10446     
10447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10448       ExitAnalyzeMode();
10449
10450     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10451         gameMode == AnalyzeFile)
10452         TruncateGame();
10453
10454     ResurrectChessProgram();    /* in case it isn't running */
10455     gameMode = MachinePlaysBlack;
10456     pausing = FALSE;
10457     ModeHighlight();
10458     SetGameInfo();
10459     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10460     DisplayTitle(buf);
10461     if (first.sendName) {
10462       sprintf(buf, "name %s\n", gameInfo.white);
10463       SendToProgram(buf, &first);
10464     }
10465     if (first.sendTime) {
10466       if (first.useColors) {
10467         SendToProgram("white\n", &first); /*gnu kludge*/
10468       }
10469       SendTimeRemaining(&first, FALSE);
10470     }
10471     if (first.useColors) {
10472       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10473     }
10474     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10475     SetMachineThinkingEnables();
10476     first.maybeThinking = TRUE;
10477     StartClocks();
10478
10479     if (appData.autoFlipView && flipView) {
10480       flipView = !flipView;
10481       DrawPosition(FALSE, NULL);
10482       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10483     }
10484     if(bookHit) { // [HGM] book: simulate book reply
10485         static char bookMove[MSG_SIZ]; // a bit generous?
10486
10487         programStats.nodes = programStats.depth = programStats.time = 
10488         programStats.score = programStats.got_only_move = 0;
10489         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10490
10491         strcpy(bookMove, "move ");
10492         strcat(bookMove, bookHit);
10493         HandleMachineMove(bookMove, &first);
10494     }
10495 }
10496
10497
10498 void
10499 DisplayTwoMachinesTitle()
10500 {
10501     char buf[MSG_SIZ];
10502     if (appData.matchGames > 0) {
10503         if (first.twoMachinesColor[0] == 'w') {
10504             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10505                     gameInfo.white, gameInfo.black,
10506                     first.matchWins, second.matchWins,
10507                     matchGame - 1 - (first.matchWins + second.matchWins));
10508         } else {
10509             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10510                     gameInfo.white, gameInfo.black,
10511                     second.matchWins, first.matchWins,
10512                     matchGame - 1 - (first.matchWins + second.matchWins));
10513         }
10514     } else {
10515         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10516     }
10517     DisplayTitle(buf);
10518 }
10519
10520 void
10521 TwoMachinesEvent P((void))
10522 {
10523     int i;
10524     char buf[MSG_SIZ];
10525     ChessProgramState *onmove;
10526     char *bookHit = NULL;
10527     
10528     if (appData.noChessProgram) return;
10529
10530     switch (gameMode) {
10531       case TwoMachinesPlay:
10532         return;
10533       case MachinePlaysWhite:
10534       case MachinePlaysBlack:
10535         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10536             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10537             return;
10538         }
10539         /* fall through */
10540       case BeginningOfGame:
10541       case PlayFromGameFile:
10542       case EndOfGame:
10543         EditGameEvent();
10544         if (gameMode != EditGame) return;
10545         break;
10546       case EditPosition:
10547         EditPositionDone();
10548         break;
10549       case AnalyzeMode:
10550       case AnalyzeFile:
10551         ExitAnalyzeMode();
10552         break;
10553       case EditGame:
10554       default:
10555         break;
10556     }
10557
10558     forwardMostMove = currentMove;
10559     ResurrectChessProgram();    /* in case first program isn't running */
10560
10561     if (second.pr == NULL) {
10562         StartChessProgram(&second);
10563         if (second.protocolVersion == 1) {
10564           TwoMachinesEventIfReady();
10565         } else {
10566           /* kludge: allow timeout for initial "feature" command */
10567           FreezeUI();
10568           DisplayMessage("", _("Starting second chess program"));
10569           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10570         }
10571         return;
10572     }
10573     DisplayMessage("", "");
10574     InitChessProgram(&second, FALSE);
10575     SendToProgram("force\n", &second);
10576     if (startedFromSetupPosition) {
10577         SendBoard(&second, backwardMostMove);
10578     if (appData.debugMode) {
10579         fprintf(debugFP, "Two Machines\n");
10580     }
10581     }
10582     for (i = backwardMostMove; i < forwardMostMove; i++) {
10583         SendMoveToProgram(i, &second);
10584     }
10585
10586     gameMode = TwoMachinesPlay;
10587     pausing = FALSE;
10588     ModeHighlight();
10589     SetGameInfo();
10590     DisplayTwoMachinesTitle();
10591     firstMove = TRUE;
10592     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10593         onmove = &first;
10594     } else {
10595         onmove = &second;
10596     }
10597
10598     SendToProgram(first.computerString, &first);
10599     if (first.sendName) {
10600       sprintf(buf, "name %s\n", second.tidy);
10601       SendToProgram(buf, &first);
10602     }
10603     SendToProgram(second.computerString, &second);
10604     if (second.sendName) {
10605       sprintf(buf, "name %s\n", first.tidy);
10606       SendToProgram(buf, &second);
10607     }
10608
10609     ResetClocks();
10610     if (!first.sendTime || !second.sendTime) {
10611         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10612         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10613     }
10614     if (onmove->sendTime) {
10615       if (onmove->useColors) {
10616         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10617       }
10618       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10619     }
10620     if (onmove->useColors) {
10621       SendToProgram(onmove->twoMachinesColor, onmove);
10622     }
10623     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10624 //    SendToProgram("go\n", onmove);
10625     onmove->maybeThinking = TRUE;
10626     SetMachineThinkingEnables();
10627
10628     StartClocks();
10629
10630     if(bookHit) { // [HGM] book: simulate book reply
10631         static char bookMove[MSG_SIZ]; // a bit generous?
10632
10633         programStats.nodes = programStats.depth = programStats.time = 
10634         programStats.score = programStats.got_only_move = 0;
10635         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10636
10637         strcpy(bookMove, "move ");
10638         strcat(bookMove, bookHit);
10639         HandleMachineMove(bookMove, &first);
10640     }
10641 }
10642
10643 void
10644 TrainingEvent()
10645 {
10646     if (gameMode == Training) {
10647       SetTrainingModeOff();
10648       gameMode = PlayFromGameFile;
10649       DisplayMessage("", _("Training mode off"));
10650     } else {
10651       gameMode = Training;
10652       animateTraining = appData.animate;
10653
10654       /* make sure we are not already at the end of the game */
10655       if (currentMove < forwardMostMove) {
10656         SetTrainingModeOn();
10657         DisplayMessage("", _("Training mode on"));
10658       } else {
10659         gameMode = PlayFromGameFile;
10660         DisplayError(_("Already at end of game"), 0);
10661       }
10662     }
10663     ModeHighlight();
10664 }
10665
10666 void
10667 IcsClientEvent()
10668 {
10669     if (!appData.icsActive) return;
10670     switch (gameMode) {
10671       case IcsPlayingWhite:
10672       case IcsPlayingBlack:
10673       case IcsObserving:
10674       case IcsIdle:
10675       case BeginningOfGame:
10676       case IcsExamining:
10677         return;
10678
10679       case EditGame:
10680         break;
10681
10682       case EditPosition:
10683         EditPositionDone();
10684         break;
10685
10686       case AnalyzeMode:
10687       case AnalyzeFile:
10688         ExitAnalyzeMode();
10689         break;
10690         
10691       default:
10692         EditGameEvent();
10693         break;
10694     }
10695
10696     gameMode = IcsIdle;
10697     ModeHighlight();
10698     return;
10699 }
10700
10701
10702 void
10703 EditGameEvent()
10704 {
10705     int i;
10706
10707     switch (gameMode) {
10708       case Training:
10709         SetTrainingModeOff();
10710         break;
10711       case MachinePlaysWhite:
10712       case MachinePlaysBlack:
10713       case BeginningOfGame:
10714         SendToProgram("force\n", &first);
10715         SetUserThinkingEnables();
10716         break;
10717       case PlayFromGameFile:
10718         (void) StopLoadGameTimer();
10719         if (gameFileFP != NULL) {
10720             gameFileFP = NULL;
10721         }
10722         break;
10723       case EditPosition:
10724         EditPositionDone();
10725         break;
10726       case AnalyzeMode:
10727       case AnalyzeFile:
10728         ExitAnalyzeMode();
10729         SendToProgram("force\n", &first);
10730         break;
10731       case TwoMachinesPlay:
10732         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10733         ResurrectChessProgram();
10734         SetUserThinkingEnables();
10735         break;
10736       case EndOfGame:
10737         ResurrectChessProgram();
10738         break;
10739       case IcsPlayingBlack:
10740       case IcsPlayingWhite:
10741         DisplayError(_("Warning: You are still playing a game"), 0);
10742         break;
10743       case IcsObserving:
10744         DisplayError(_("Warning: You are still observing a game"), 0);
10745         break;
10746       case IcsExamining:
10747         DisplayError(_("Warning: You are still examining a game"), 0);
10748         break;
10749       case IcsIdle:
10750         break;
10751       case EditGame:
10752       default:
10753         return;
10754     }
10755     
10756     pausing = FALSE;
10757     StopClocks();
10758     first.offeredDraw = second.offeredDraw = 0;
10759
10760     if (gameMode == PlayFromGameFile) {
10761         whiteTimeRemaining = timeRemaining[0][currentMove];
10762         blackTimeRemaining = timeRemaining[1][currentMove];
10763         DisplayTitle("");
10764     }
10765
10766     if (gameMode == MachinePlaysWhite ||
10767         gameMode == MachinePlaysBlack ||
10768         gameMode == TwoMachinesPlay ||
10769         gameMode == EndOfGame) {
10770         i = forwardMostMove;
10771         while (i > currentMove) {
10772             SendToProgram("undo\n", &first);
10773             i--;
10774         }
10775         whiteTimeRemaining = timeRemaining[0][currentMove];
10776         blackTimeRemaining = timeRemaining[1][currentMove];
10777         DisplayBothClocks();
10778         if (whiteFlag || blackFlag) {
10779             whiteFlag = blackFlag = 0;
10780         }
10781         DisplayTitle("");
10782     }           
10783     
10784     gameMode = EditGame;
10785     ModeHighlight();
10786     SetGameInfo();
10787 }
10788
10789
10790 void
10791 EditPositionEvent()
10792 {
10793     if (gameMode == EditPosition) {
10794         EditGameEvent();
10795         return;
10796     }
10797     
10798     EditGameEvent();
10799     if (gameMode != EditGame) return;
10800     
10801     gameMode = EditPosition;
10802     ModeHighlight();
10803     SetGameInfo();
10804     if (currentMove > 0)
10805       CopyBoard(boards[0], boards[currentMove]);
10806     
10807     blackPlaysFirst = !WhiteOnMove(currentMove);
10808     ResetClocks();
10809     currentMove = forwardMostMove = backwardMostMove = 0;
10810     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10811     DisplayMove(-1);
10812 }
10813
10814 void
10815 ExitAnalyzeMode()
10816 {
10817     /* [DM] icsEngineAnalyze - possible call from other functions */
10818     if (appData.icsEngineAnalyze) {
10819         appData.icsEngineAnalyze = FALSE;
10820
10821         DisplayMessage("",_("Close ICS engine analyze..."));
10822     }
10823     if (first.analysisSupport && first.analyzing) {
10824       SendToProgram("exit\n", &first);
10825       first.analyzing = FALSE;
10826     }
10827     AnalysisPopDown();
10828     thinkOutput[0] = NULLCHAR;
10829 }
10830
10831 void
10832 EditPositionDone()
10833 {
10834     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10835
10836     startedFromSetupPosition = TRUE;
10837     InitChessProgram(&first, FALSE);
10838     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10839     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10840         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10841         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10842     } else castlingRights[0][2] = -1;
10843     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10844         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10845         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10846     } else castlingRights[0][5] = -1;
10847     SendToProgram("force\n", &first);
10848     if (blackPlaysFirst) {
10849         strcpy(moveList[0], "");
10850         strcpy(parseList[0], "");
10851         currentMove = forwardMostMove = backwardMostMove = 1;
10852         CopyBoard(boards[1], boards[0]);
10853         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10854         { int i;
10855           epStatus[1] = epStatus[0];
10856           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10857         }
10858     } else {
10859         currentMove = forwardMostMove = backwardMostMove = 0;
10860     }
10861     SendBoard(&first, forwardMostMove);
10862     if (appData.debugMode) {
10863         fprintf(debugFP, "EditPosDone\n");
10864     }
10865     DisplayTitle("");
10866     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10867     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10868     gameMode = EditGame;
10869     ModeHighlight();
10870     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10871     ClearHighlights(); /* [AS] */
10872 }
10873
10874 /* Pause for `ms' milliseconds */
10875 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10876 void
10877 TimeDelay(ms)
10878      long ms;
10879 {
10880     TimeMark m1, m2;
10881
10882     GetTimeMark(&m1);
10883     do {
10884         GetTimeMark(&m2);
10885     } while (SubtractTimeMarks(&m2, &m1) < ms);
10886 }
10887
10888 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10889 void
10890 SendMultiLineToICS(buf)
10891      char *buf;
10892 {
10893     char temp[MSG_SIZ+1], *p;
10894     int len;
10895
10896     len = strlen(buf);
10897     if (len > MSG_SIZ)
10898       len = MSG_SIZ;
10899   
10900     strncpy(temp, buf, len);
10901     temp[len] = 0;
10902
10903     p = temp;
10904     while (*p) {
10905         if (*p == '\n' || *p == '\r')
10906           *p = ' ';
10907         ++p;
10908     }
10909
10910     strcat(temp, "\n");
10911     SendToICS(temp);
10912     SendToPlayer(temp, strlen(temp));
10913 }
10914
10915 void
10916 SetWhiteToPlayEvent()
10917 {
10918     if (gameMode == EditPosition) {
10919         blackPlaysFirst = FALSE;
10920         DisplayBothClocks();    /* works because currentMove is 0 */
10921     } else if (gameMode == IcsExamining) {
10922         SendToICS(ics_prefix);
10923         SendToICS("tomove white\n");
10924     }
10925 }
10926
10927 void
10928 SetBlackToPlayEvent()
10929 {
10930     if (gameMode == EditPosition) {
10931         blackPlaysFirst = TRUE;
10932         currentMove = 1;        /* kludge */
10933         DisplayBothClocks();
10934         currentMove = 0;
10935     } else if (gameMode == IcsExamining) {
10936         SendToICS(ics_prefix);
10937         SendToICS("tomove black\n");
10938     }
10939 }
10940
10941 void
10942 EditPositionMenuEvent(selection, x, y)
10943      ChessSquare selection;
10944      int x, y;
10945 {
10946     char buf[MSG_SIZ];
10947     ChessSquare piece = boards[0][y][x];
10948
10949     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10950
10951     switch (selection) {
10952       case ClearBoard:
10953         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10954             SendToICS(ics_prefix);
10955             SendToICS("bsetup clear\n");
10956         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10957             SendToICS(ics_prefix);
10958             SendToICS("clearboard\n");
10959         } else {
10960             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10961                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10962                 for (y = 0; y < BOARD_HEIGHT; y++) {
10963                     if (gameMode == IcsExamining) {
10964                         if (boards[currentMove][y][x] != EmptySquare) {
10965                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10966                                     AAA + x, ONE + y);
10967                             SendToICS(buf);
10968                         }
10969                     } else {
10970                         boards[0][y][x] = p;
10971                     }
10972                 }
10973             }
10974         }
10975         if (gameMode == EditPosition) {
10976             DrawPosition(FALSE, boards[0]);
10977         }
10978         break;
10979
10980       case WhitePlay:
10981         SetWhiteToPlayEvent();
10982         break;
10983
10984       case BlackPlay:
10985         SetBlackToPlayEvent();
10986         break;
10987
10988       case EmptySquare:
10989         if (gameMode == IcsExamining) {
10990             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
10991             SendToICS(buf);
10992         } else {
10993             boards[0][y][x] = EmptySquare;
10994             DrawPosition(FALSE, boards[0]);
10995         }
10996         break;
10997
10998       case PromotePiece:
10999         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11000            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11001             selection = (ChessSquare) (PROMOTED piece);
11002         } else if(piece == EmptySquare) selection = WhiteSilver;
11003         else selection = (ChessSquare)((int)piece - 1);
11004         goto defaultlabel;
11005
11006       case DemotePiece:
11007         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11008            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11009             selection = (ChessSquare) (DEMOTED piece);
11010         } else if(piece == EmptySquare) selection = BlackSilver;
11011         else selection = (ChessSquare)((int)piece + 1);       
11012         goto defaultlabel;
11013
11014       case WhiteQueen:
11015       case BlackQueen:
11016         if(gameInfo.variant == VariantShatranj ||
11017            gameInfo.variant == VariantXiangqi  ||
11018            gameInfo.variant == VariantCourier    )
11019             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11020         goto defaultlabel;
11021
11022       case WhiteKing:
11023       case BlackKing:
11024         if(gameInfo.variant == VariantXiangqi)
11025             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11026         if(gameInfo.variant == VariantKnightmate)
11027             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11028       default:
11029         defaultlabel:
11030         if (gameMode == IcsExamining) {
11031             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11032                     PieceToChar(selection), AAA + x, ONE + y);
11033             SendToICS(buf);
11034         } else {
11035             boards[0][y][x] = selection;
11036             DrawPosition(FALSE, boards[0]);
11037         }
11038         break;
11039     }
11040 }
11041
11042
11043 void
11044 DropMenuEvent(selection, x, y)
11045      ChessSquare selection;
11046      int x, y;
11047 {
11048     ChessMove moveType;
11049
11050     switch (gameMode) {
11051       case IcsPlayingWhite:
11052       case MachinePlaysBlack:
11053         if (!WhiteOnMove(currentMove)) {
11054             DisplayMoveError(_("It is Black's turn"));
11055             return;
11056         }
11057         moveType = WhiteDrop;
11058         break;
11059       case IcsPlayingBlack:
11060       case MachinePlaysWhite:
11061         if (WhiteOnMove(currentMove)) {
11062             DisplayMoveError(_("It is White's turn"));
11063             return;
11064         }
11065         moveType = BlackDrop;
11066         break;
11067       case EditGame:
11068         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11069         break;
11070       default:
11071         return;
11072     }
11073
11074     if (moveType == BlackDrop && selection < BlackPawn) {
11075       selection = (ChessSquare) ((int) selection
11076                                  + (int) BlackPawn - (int) WhitePawn);
11077     }
11078     if (boards[currentMove][y][x] != EmptySquare) {
11079         DisplayMoveError(_("That square is occupied"));
11080         return;
11081     }
11082
11083     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11084 }
11085
11086 void
11087 AcceptEvent()
11088 {
11089     /* Accept a pending offer of any kind from opponent */
11090     
11091     if (appData.icsActive) {
11092         SendToICS(ics_prefix);
11093         SendToICS("accept\n");
11094     } else if (cmailMsgLoaded) {
11095         if (currentMove == cmailOldMove &&
11096             commentList[cmailOldMove] != NULL &&
11097             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11098                    "Black offers a draw" : "White offers a draw")) {
11099             TruncateGame();
11100             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11101             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11102         } else {
11103             DisplayError(_("There is no pending offer on this move"), 0);
11104             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11105         }
11106     } else {
11107         /* Not used for offers from chess program */
11108     }
11109 }
11110
11111 void
11112 DeclineEvent()
11113 {
11114     /* Decline a pending offer of any kind from opponent */
11115     
11116     if (appData.icsActive) {
11117         SendToICS(ics_prefix);
11118         SendToICS("decline\n");
11119     } else if (cmailMsgLoaded) {
11120         if (currentMove == cmailOldMove &&
11121             commentList[cmailOldMove] != NULL &&
11122             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11123                    "Black offers a draw" : "White offers a draw")) {
11124 #ifdef NOTDEF
11125             AppendComment(cmailOldMove, "Draw declined");
11126             DisplayComment(cmailOldMove - 1, "Draw declined");
11127 #endif /*NOTDEF*/
11128         } else {
11129             DisplayError(_("There is no pending offer on this move"), 0);
11130         }
11131     } else {
11132         /* Not used for offers from chess program */
11133     }
11134 }
11135
11136 void
11137 RematchEvent()
11138 {
11139     /* Issue ICS rematch command */
11140     if (appData.icsActive) {
11141         SendToICS(ics_prefix);
11142         SendToICS("rematch\n");
11143     }
11144 }
11145
11146 void
11147 CallFlagEvent()
11148 {
11149     /* Call your opponent's flag (claim a win on time) */
11150     if (appData.icsActive) {
11151         SendToICS(ics_prefix);
11152         SendToICS("flag\n");
11153     } else {
11154         switch (gameMode) {
11155           default:
11156             return;
11157           case MachinePlaysWhite:
11158             if (whiteFlag) {
11159                 if (blackFlag)
11160                   GameEnds(GameIsDrawn, "Both players ran out of time",
11161                            GE_PLAYER);
11162                 else
11163                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11164             } else {
11165                 DisplayError(_("Your opponent is not out of time"), 0);
11166             }
11167             break;
11168           case MachinePlaysBlack:
11169             if (blackFlag) {
11170                 if (whiteFlag)
11171                   GameEnds(GameIsDrawn, "Both players ran out of time",
11172                            GE_PLAYER);
11173                 else
11174                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11175             } else {
11176                 DisplayError(_("Your opponent is not out of time"), 0);
11177             }
11178             break;
11179         }
11180     }
11181 }
11182
11183 void
11184 DrawEvent()
11185 {
11186     /* Offer draw or accept pending draw offer from opponent */
11187     
11188     if (appData.icsActive) {
11189         /* Note: tournament rules require draw offers to be
11190            made after you make your move but before you punch
11191            your clock.  Currently ICS doesn't let you do that;
11192            instead, you immediately punch your clock after making
11193            a move, but you can offer a draw at any time. */
11194         
11195         SendToICS(ics_prefix);
11196         SendToICS("draw\n");
11197     } else if (cmailMsgLoaded) {
11198         if (currentMove == cmailOldMove &&
11199             commentList[cmailOldMove] != NULL &&
11200             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11201                    "Black offers a draw" : "White offers a draw")) {
11202             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11203             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11204         } else if (currentMove == cmailOldMove + 1) {
11205             char *offer = WhiteOnMove(cmailOldMove) ?
11206               "White offers a draw" : "Black offers a draw";
11207             AppendComment(currentMove, offer);
11208             DisplayComment(currentMove - 1, offer);
11209             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11210         } else {
11211             DisplayError(_("You must make your move before offering a draw"), 0);
11212             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11213         }
11214     } else if (first.offeredDraw) {
11215         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11216     } else {
11217         if (first.sendDrawOffers) {
11218             SendToProgram("draw\n", &first);
11219             userOfferedDraw = TRUE;
11220         }
11221     }
11222 }
11223
11224 void
11225 AdjournEvent()
11226 {
11227     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11228     
11229     if (appData.icsActive) {
11230         SendToICS(ics_prefix);
11231         SendToICS("adjourn\n");
11232     } else {
11233         /* Currently GNU Chess doesn't offer or accept Adjourns */
11234     }
11235 }
11236
11237
11238 void
11239 AbortEvent()
11240 {
11241     /* Offer Abort or accept pending Abort offer from opponent */
11242     
11243     if (appData.icsActive) {
11244         SendToICS(ics_prefix);
11245         SendToICS("abort\n");
11246     } else {
11247         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11248     }
11249 }
11250
11251 void
11252 ResignEvent()
11253 {
11254     /* Resign.  You can do this even if it's not your turn. */
11255     
11256     if (appData.icsActive) {
11257         SendToICS(ics_prefix);
11258         SendToICS("resign\n");
11259     } else {
11260         switch (gameMode) {
11261           case MachinePlaysWhite:
11262             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11263             break;
11264           case MachinePlaysBlack:
11265             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11266             break;
11267           case EditGame:
11268             if (cmailMsgLoaded) {
11269                 TruncateGame();
11270                 if (WhiteOnMove(cmailOldMove)) {
11271                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11272                 } else {
11273                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11274                 }
11275                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11276             }
11277             break;
11278           default:
11279             break;
11280         }
11281     }
11282 }
11283
11284
11285 void
11286 StopObservingEvent()
11287 {
11288     /* Stop observing current games */
11289     SendToICS(ics_prefix);
11290     SendToICS("unobserve\n");
11291 }
11292
11293 void
11294 StopExaminingEvent()
11295 {
11296     /* Stop observing current game */
11297     SendToICS(ics_prefix);
11298     SendToICS("unexamine\n");
11299 }
11300
11301 void
11302 ForwardInner(target)
11303      int target;
11304 {
11305     int limit;
11306
11307     if (appData.debugMode)
11308         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11309                 target, currentMove, forwardMostMove);
11310
11311     if (gameMode == EditPosition)
11312       return;
11313
11314     if (gameMode == PlayFromGameFile && !pausing)
11315       PauseEvent();
11316     
11317     if (gameMode == IcsExamining && pausing)
11318       limit = pauseExamForwardMostMove;
11319     else
11320       limit = forwardMostMove;
11321     
11322     if (target > limit) target = limit;
11323
11324     if (target > 0 && moveList[target - 1][0]) {
11325         int fromX, fromY, toX, toY;
11326         toX = moveList[target - 1][2] - AAA;
11327         toY = moveList[target - 1][3] - ONE;
11328         if (moveList[target - 1][1] == '@') {
11329             if (appData.highlightLastMove) {
11330                 SetHighlights(-1, -1, toX, toY);
11331             }
11332         } else {
11333             fromX = moveList[target - 1][0] - AAA;
11334             fromY = moveList[target - 1][1] - ONE;
11335             if (target == currentMove + 1) {
11336                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11337             }
11338             if (appData.highlightLastMove) {
11339                 SetHighlights(fromX, fromY, toX, toY);
11340             }
11341         }
11342     }
11343     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11344         gameMode == Training || gameMode == PlayFromGameFile || 
11345         gameMode == AnalyzeFile) {
11346         while (currentMove < target) {
11347             SendMoveToProgram(currentMove++, &first);
11348         }
11349     } else {
11350         currentMove = target;
11351     }
11352     
11353     if (gameMode == EditGame || gameMode == EndOfGame) {
11354         whiteTimeRemaining = timeRemaining[0][currentMove];
11355         blackTimeRemaining = timeRemaining[1][currentMove];
11356     }
11357     DisplayBothClocks();
11358     DisplayMove(currentMove - 1);
11359     DrawPosition(FALSE, boards[currentMove]);
11360     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11361     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11362         DisplayComment(currentMove - 1, commentList[currentMove]);
11363     }
11364 }
11365
11366
11367 void
11368 ForwardEvent()
11369 {
11370     if (gameMode == IcsExamining && !pausing) {
11371         SendToICS(ics_prefix);
11372         SendToICS("forward\n");
11373     } else {
11374         ForwardInner(currentMove + 1);
11375     }
11376 }
11377
11378 void
11379 ToEndEvent()
11380 {
11381     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11382         /* to optimze, we temporarily turn off analysis mode while we feed
11383          * the remaining moves to the engine. Otherwise we get analysis output
11384          * after each move.
11385          */ 
11386         if (first.analysisSupport) {
11387           SendToProgram("exit\nforce\n", &first);
11388           first.analyzing = FALSE;
11389         }
11390     }
11391         
11392     if (gameMode == IcsExamining && !pausing) {
11393         SendToICS(ics_prefix);
11394         SendToICS("forward 999999\n");
11395     } else {
11396         ForwardInner(forwardMostMove);
11397     }
11398
11399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11400         /* we have fed all the moves, so reactivate analysis mode */
11401         SendToProgram("analyze\n", &first);
11402         first.analyzing = TRUE;
11403         /*first.maybeThinking = TRUE;*/
11404         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11405     }
11406 }
11407
11408 void
11409 BackwardInner(target)
11410      int target;
11411 {
11412     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11413
11414     if (appData.debugMode)
11415         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11416                 target, currentMove, forwardMostMove);
11417
11418     if (gameMode == EditPosition) return;
11419     if (currentMove <= backwardMostMove) {
11420         ClearHighlights();
11421         DrawPosition(full_redraw, boards[currentMove]);
11422         return;
11423     }
11424     if (gameMode == PlayFromGameFile && !pausing)
11425       PauseEvent();
11426     
11427     if (moveList[target][0]) {
11428         int fromX, fromY, toX, toY;
11429         toX = moveList[target][2] - AAA;
11430         toY = moveList[target][3] - ONE;
11431         if (moveList[target][1] == '@') {
11432             if (appData.highlightLastMove) {
11433                 SetHighlights(-1, -1, toX, toY);
11434             }
11435         } else {
11436             fromX = moveList[target][0] - AAA;
11437             fromY = moveList[target][1] - ONE;
11438             if (target == currentMove - 1) {
11439                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11440             }
11441             if (appData.highlightLastMove) {
11442                 SetHighlights(fromX, fromY, toX, toY);
11443             }
11444         }
11445     }
11446     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11447         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11448         while (currentMove > target) {
11449             SendToProgram("undo\n", &first);
11450             currentMove--;
11451         }
11452     } else {
11453         currentMove = target;
11454     }
11455     
11456     if (gameMode == EditGame || gameMode == EndOfGame) {
11457         whiteTimeRemaining = timeRemaining[0][currentMove];
11458         blackTimeRemaining = timeRemaining[1][currentMove];
11459     }
11460     DisplayBothClocks();
11461     DisplayMove(currentMove - 1);
11462     DrawPosition(full_redraw, boards[currentMove]);
11463     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11464     // [HGM] PV info: routine tests if comment empty
11465     DisplayComment(currentMove - 1, commentList[currentMove]);
11466 }
11467
11468 void
11469 BackwardEvent()
11470 {
11471     if (gameMode == IcsExamining && !pausing) {
11472         SendToICS(ics_prefix);
11473         SendToICS("backward\n");
11474     } else {
11475         BackwardInner(currentMove - 1);
11476     }
11477 }
11478
11479 void
11480 ToStartEvent()
11481 {
11482     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11483         /* to optimze, we temporarily turn off analysis mode while we undo
11484          * all the moves. Otherwise we get analysis output after each undo.
11485          */ 
11486         if (first.analysisSupport) {
11487           SendToProgram("exit\nforce\n", &first);
11488           first.analyzing = FALSE;
11489         }
11490     }
11491
11492     if (gameMode == IcsExamining && !pausing) {
11493         SendToICS(ics_prefix);
11494         SendToICS("backward 999999\n");
11495     } else {
11496         BackwardInner(backwardMostMove);
11497     }
11498
11499     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11500         /* we have fed all the moves, so reactivate analysis mode */
11501         SendToProgram("analyze\n", &first);
11502         first.analyzing = TRUE;
11503         /*first.maybeThinking = TRUE;*/
11504         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11505     }
11506 }
11507
11508 void
11509 ToNrEvent(int to)
11510 {
11511   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11512   if (to >= forwardMostMove) to = forwardMostMove;
11513   if (to <= backwardMostMove) to = backwardMostMove;
11514   if (to < currentMove) {
11515     BackwardInner(to);
11516   } else {
11517     ForwardInner(to);
11518   }
11519 }
11520
11521 void
11522 RevertEvent()
11523 {
11524     if (gameMode != IcsExamining) {
11525         DisplayError(_("You are not examining a game"), 0);
11526         return;
11527     }
11528     if (pausing) {
11529         DisplayError(_("You can't revert while pausing"), 0);
11530         return;
11531     }
11532     SendToICS(ics_prefix);
11533     SendToICS("revert\n");
11534 }
11535
11536 void
11537 RetractMoveEvent()
11538 {
11539     switch (gameMode) {
11540       case MachinePlaysWhite:
11541       case MachinePlaysBlack:
11542         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11543             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11544             return;
11545         }
11546         if (forwardMostMove < 2) return;
11547         currentMove = forwardMostMove = forwardMostMove - 2;
11548         whiteTimeRemaining = timeRemaining[0][currentMove];
11549         blackTimeRemaining = timeRemaining[1][currentMove];
11550         DisplayBothClocks();
11551         DisplayMove(currentMove - 1);
11552         ClearHighlights();/*!! could figure this out*/
11553         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11554         SendToProgram("remove\n", &first);
11555         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11556         break;
11557
11558       case BeginningOfGame:
11559       default:
11560         break;
11561
11562       case IcsPlayingWhite:
11563       case IcsPlayingBlack:
11564         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11565             SendToICS(ics_prefix);
11566             SendToICS("takeback 2\n");
11567         } else {
11568             SendToICS(ics_prefix);
11569             SendToICS("takeback 1\n");
11570         }
11571         break;
11572     }
11573 }
11574
11575 void
11576 MoveNowEvent()
11577 {
11578     ChessProgramState *cps;
11579
11580     switch (gameMode) {
11581       case MachinePlaysWhite:
11582         if (!WhiteOnMove(forwardMostMove)) {
11583             DisplayError(_("It is your turn"), 0);
11584             return;
11585         }
11586         cps = &first;
11587         break;
11588       case MachinePlaysBlack:
11589         if (WhiteOnMove(forwardMostMove)) {
11590             DisplayError(_("It is your turn"), 0);
11591             return;
11592         }
11593         cps = &first;
11594         break;
11595       case TwoMachinesPlay:
11596         if (WhiteOnMove(forwardMostMove) ==
11597             (first.twoMachinesColor[0] == 'w')) {
11598             cps = &first;
11599         } else {
11600             cps = &second;
11601         }
11602         break;
11603       case BeginningOfGame:
11604       default:
11605         return;
11606     }
11607     SendToProgram("?\n", cps);
11608 }
11609
11610 void
11611 TruncateGameEvent()
11612 {
11613     EditGameEvent();
11614     if (gameMode != EditGame) return;
11615     TruncateGame();
11616 }
11617
11618 void
11619 TruncateGame()
11620 {
11621     if (forwardMostMove > currentMove) {
11622         if (gameInfo.resultDetails != NULL) {
11623             free(gameInfo.resultDetails);
11624             gameInfo.resultDetails = NULL;
11625             gameInfo.result = GameUnfinished;
11626         }
11627         forwardMostMove = currentMove;
11628         HistorySet(parseList, backwardMostMove, forwardMostMove,
11629                    currentMove-1);
11630     }
11631 }
11632
11633 void
11634 HintEvent()
11635 {
11636     if (appData.noChessProgram) return;
11637     switch (gameMode) {
11638       case MachinePlaysWhite:
11639         if (WhiteOnMove(forwardMostMove)) {
11640             DisplayError(_("Wait until your turn"), 0);
11641             return;
11642         }
11643         break;
11644       case BeginningOfGame:
11645       case MachinePlaysBlack:
11646         if (!WhiteOnMove(forwardMostMove)) {
11647             DisplayError(_("Wait until your turn"), 0);
11648             return;
11649         }
11650         break;
11651       default:
11652         DisplayError(_("No hint available"), 0);
11653         return;
11654     }
11655     SendToProgram("hint\n", &first);
11656     hintRequested = TRUE;
11657 }
11658
11659 void
11660 BookEvent()
11661 {
11662     if (appData.noChessProgram) return;
11663     switch (gameMode) {
11664       case MachinePlaysWhite:
11665         if (WhiteOnMove(forwardMostMove)) {
11666             DisplayError(_("Wait until your turn"), 0);
11667             return;
11668         }
11669         break;
11670       case BeginningOfGame:
11671       case MachinePlaysBlack:
11672         if (!WhiteOnMove(forwardMostMove)) {
11673             DisplayError(_("Wait until your turn"), 0);
11674             return;
11675         }
11676         break;
11677       case EditPosition:
11678         EditPositionDone();
11679         break;
11680       case TwoMachinesPlay:
11681         return;
11682       default:
11683         break;
11684     }
11685     SendToProgram("bk\n", &first);
11686     bookOutput[0] = NULLCHAR;
11687     bookRequested = TRUE;
11688 }
11689
11690 void
11691 AboutGameEvent()
11692 {
11693     char *tags = PGNTags(&gameInfo);
11694     TagsPopUp(tags, CmailMsg());
11695     free(tags);
11696 }
11697
11698 /* end button procedures */
11699
11700 void
11701 PrintPosition(fp, move)
11702      FILE *fp;
11703      int move;
11704 {
11705     int i, j;
11706     
11707     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11708         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11709             char c = PieceToChar(boards[move][i][j]);
11710             fputc(c == 'x' ? '.' : c, fp);
11711             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11712         }
11713     }
11714     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11715       fprintf(fp, "white to play\n");
11716     else
11717       fprintf(fp, "black to play\n");
11718 }
11719
11720 void
11721 PrintOpponents(fp)
11722      FILE *fp;
11723 {
11724     if (gameInfo.white != NULL) {
11725         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11726     } else {
11727         fprintf(fp, "\n");
11728     }
11729 }
11730
11731 /* Find last component of program's own name, using some heuristics */
11732 void
11733 TidyProgramName(prog, host, buf)
11734      char *prog, *host, buf[MSG_SIZ];
11735 {
11736     char *p, *q;
11737     int local = (strcmp(host, "localhost") == 0);
11738     while (!local && (p = strchr(prog, ';')) != NULL) {
11739         p++;
11740         while (*p == ' ') p++;
11741         prog = p;
11742     }
11743     if (*prog == '"' || *prog == '\'') {
11744         q = strchr(prog + 1, *prog);
11745     } else {
11746         q = strchr(prog, ' ');
11747     }
11748     if (q == NULL) q = prog + strlen(prog);
11749     p = q;
11750     while (p >= prog && *p != '/' && *p != '\\') p--;
11751     p++;
11752     if(p == prog && *p == '"') p++;
11753     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11754     memcpy(buf, p, q - p);
11755     buf[q - p] = NULLCHAR;
11756     if (!local) {
11757         strcat(buf, "@");
11758         strcat(buf, host);
11759     }
11760 }
11761
11762 char *
11763 TimeControlTagValue()
11764 {
11765     char buf[MSG_SIZ];
11766     if (!appData.clockMode) {
11767         strcpy(buf, "-");
11768     } else if (movesPerSession > 0) {
11769         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11770     } else if (timeIncrement == 0) {
11771         sprintf(buf, "%ld", timeControl/1000);
11772     } else {
11773         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11774     }
11775     return StrSave(buf);
11776 }
11777
11778 void
11779 SetGameInfo()
11780 {
11781     /* This routine is used only for certain modes */
11782     VariantClass v = gameInfo.variant;
11783     ClearGameInfo(&gameInfo);
11784     gameInfo.variant = v;
11785
11786     switch (gameMode) {
11787       case MachinePlaysWhite:
11788         gameInfo.event = StrSave( appData.pgnEventHeader );
11789         gameInfo.site = StrSave(HostName());
11790         gameInfo.date = PGNDate();
11791         gameInfo.round = StrSave("-");
11792         gameInfo.white = StrSave(first.tidy);
11793         gameInfo.black = StrSave(UserName());
11794         gameInfo.timeControl = TimeControlTagValue();
11795         break;
11796
11797       case MachinePlaysBlack:
11798         gameInfo.event = StrSave( appData.pgnEventHeader );
11799         gameInfo.site = StrSave(HostName());
11800         gameInfo.date = PGNDate();
11801         gameInfo.round = StrSave("-");
11802         gameInfo.white = StrSave(UserName());
11803         gameInfo.black = StrSave(first.tidy);
11804         gameInfo.timeControl = TimeControlTagValue();
11805         break;
11806
11807       case TwoMachinesPlay:
11808         gameInfo.event = StrSave( appData.pgnEventHeader );
11809         gameInfo.site = StrSave(HostName());
11810         gameInfo.date = PGNDate();
11811         if (matchGame > 0) {
11812             char buf[MSG_SIZ];
11813             sprintf(buf, "%d", matchGame);
11814             gameInfo.round = StrSave(buf);
11815         } else {
11816             gameInfo.round = StrSave("-");
11817         }
11818         if (first.twoMachinesColor[0] == 'w') {
11819             gameInfo.white = StrSave(first.tidy);
11820             gameInfo.black = StrSave(second.tidy);
11821         } else {
11822             gameInfo.white = StrSave(second.tidy);
11823             gameInfo.black = StrSave(first.tidy);
11824         }
11825         gameInfo.timeControl = TimeControlTagValue();
11826         break;
11827
11828       case EditGame:
11829         gameInfo.event = StrSave("Edited game");
11830         gameInfo.site = StrSave(HostName());
11831         gameInfo.date = PGNDate();
11832         gameInfo.round = StrSave("-");
11833         gameInfo.white = StrSave("-");
11834         gameInfo.black = StrSave("-");
11835         break;
11836
11837       case EditPosition:
11838         gameInfo.event = StrSave("Edited position");
11839         gameInfo.site = StrSave(HostName());
11840         gameInfo.date = PGNDate();
11841         gameInfo.round = StrSave("-");
11842         gameInfo.white = StrSave("-");
11843         gameInfo.black = StrSave("-");
11844         break;
11845
11846       case IcsPlayingWhite:
11847       case IcsPlayingBlack:
11848       case IcsObserving:
11849       case IcsExamining:
11850         break;
11851
11852       case PlayFromGameFile:
11853         gameInfo.event = StrSave("Game from non-PGN file");
11854         gameInfo.site = StrSave(HostName());
11855         gameInfo.date = PGNDate();
11856         gameInfo.round = StrSave("-");
11857         gameInfo.white = StrSave("?");
11858         gameInfo.black = StrSave("?");
11859         break;
11860
11861       default:
11862         break;
11863     }
11864 }
11865
11866 void
11867 ReplaceComment(index, text)
11868      int index;
11869      char *text;
11870 {
11871     int len;
11872
11873     while (*text == '\n') text++;
11874     len = strlen(text);
11875     while (len > 0 && text[len - 1] == '\n') len--;
11876
11877     if (commentList[index] != NULL)
11878       free(commentList[index]);
11879
11880     if (len == 0) {
11881         commentList[index] = NULL;
11882         return;
11883     }
11884     commentList[index] = (char *) malloc(len + 2);
11885     strncpy(commentList[index], text, len);
11886     commentList[index][len] = '\n';
11887     commentList[index][len + 1] = NULLCHAR;
11888 }
11889
11890 void
11891 CrushCRs(text)
11892      char *text;
11893 {
11894   char *p = text;
11895   char *q = text;
11896   char ch;
11897
11898   do {
11899     ch = *p++;
11900     if (ch == '\r') continue;
11901     *q++ = ch;
11902   } while (ch != '\0');
11903 }
11904
11905 void
11906 AppendComment(index, text)
11907      int index;
11908      char *text;
11909 {
11910     int oldlen, len;
11911     char *old;
11912
11913     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11914
11915     CrushCRs(text);
11916     while (*text == '\n') text++;
11917     len = strlen(text);
11918     while (len > 0 && text[len - 1] == '\n') len--;
11919
11920     if (len == 0) return;
11921
11922     if (commentList[index] != NULL) {
11923         old = commentList[index];
11924         oldlen = strlen(old);
11925         commentList[index] = (char *) malloc(oldlen + len + 2);
11926         strcpy(commentList[index], old);
11927         free(old);
11928         strncpy(&commentList[index][oldlen], text, len);
11929         commentList[index][oldlen + len] = '\n';
11930         commentList[index][oldlen + len + 1] = NULLCHAR;
11931     } else {
11932         commentList[index] = (char *) malloc(len + 2);
11933         strncpy(commentList[index], text, len);
11934         commentList[index][len] = '\n';
11935         commentList[index][len + 1] = NULLCHAR;
11936     }
11937 }
11938
11939 static char * FindStr( char * text, char * sub_text )
11940 {
11941     char * result = strstr( text, sub_text );
11942
11943     if( result != NULL ) {
11944         result += strlen( sub_text );
11945     }
11946
11947     return result;
11948 }
11949
11950 /* [AS] Try to extract PV info from PGN comment */
11951 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11952 char *GetInfoFromComment( int index, char * text )
11953 {
11954     char * sep = text;
11955
11956     if( text != NULL && index > 0 ) {
11957         int score = 0;
11958         int depth = 0;
11959         int time = -1, sec = 0, deci;
11960         char * s_eval = FindStr( text, "[%eval " );
11961         char * s_emt = FindStr( text, "[%emt " );
11962
11963         if( s_eval != NULL || s_emt != NULL ) {
11964             /* New style */
11965             char delim;
11966
11967             if( s_eval != NULL ) {
11968                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11969                     return text;
11970                 }
11971
11972                 if( delim != ']' ) {
11973                     return text;
11974                 }
11975             }
11976
11977             if( s_emt != NULL ) {
11978             }
11979         }
11980         else {
11981             /* We expect something like: [+|-]nnn.nn/dd */
11982             int score_lo = 0;
11983
11984             sep = strchr( text, '/' );
11985             if( sep == NULL || sep < (text+4) ) {
11986                 return text;
11987             }
11988
11989             time = -1; sec = -1; deci = -1;
11990             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
11991                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
11992                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
11993                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
11994                 return text;
11995             }
11996
11997             if( score_lo < 0 || score_lo >= 100 ) {
11998                 return text;
11999             }
12000
12001             if(sec >= 0) time = 600*time + 10*sec; else
12002             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12003
12004             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12005
12006             /* [HGM] PV time: now locate end of PV info */
12007             while( *++sep >= '0' && *sep <= '9'); // strip depth
12008             if(time >= 0)
12009             while( *++sep >= '0' && *sep <= '9'); // strip time
12010             if(sec >= 0)
12011             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12012             if(deci >= 0)
12013             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12014             while(*sep == ' ') sep++;
12015         }
12016
12017         if( depth <= 0 ) {
12018             return text;
12019         }
12020
12021         if( time < 0 ) {
12022             time = -1;
12023         }
12024
12025         pvInfoList[index-1].depth = depth;
12026         pvInfoList[index-1].score = score;
12027         pvInfoList[index-1].time  = 10*time; // centi-sec
12028     }
12029     return sep;
12030 }
12031
12032 void
12033 SendToProgram(message, cps)
12034      char *message;
12035      ChessProgramState *cps;
12036 {
12037     int count, outCount, error;
12038     char buf[MSG_SIZ];
12039
12040     if (cps->pr == NULL) return;
12041     Attention(cps);
12042     
12043     if (appData.debugMode) {
12044         TimeMark now;
12045         GetTimeMark(&now);
12046         fprintf(debugFP, "%ld >%-6s: %s", 
12047                 SubtractTimeMarks(&now, &programStartTime),
12048                 cps->which, message);
12049     }
12050     
12051     count = strlen(message);
12052     outCount = OutputToProcess(cps->pr, message, count, &error);
12053     if (outCount < count && !exiting 
12054                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12055         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12056         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12057             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12058                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12059                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12060             } else {
12061                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12062             }
12063             gameInfo.resultDetails = buf;
12064         }
12065         DisplayFatalError(buf, error, 1);
12066     }
12067 }
12068
12069 void
12070 ReceiveFromProgram(isr, closure, message, count, error)
12071      InputSourceRef isr;
12072      VOIDSTAR closure;
12073      char *message;
12074      int count;
12075      int error;
12076 {
12077     char *end_str;
12078     char buf[MSG_SIZ];
12079     ChessProgramState *cps = (ChessProgramState *)closure;
12080
12081     if (isr != cps->isr) return; /* Killed intentionally */
12082     if (count <= 0) {
12083         if (count == 0) {
12084             sprintf(buf,
12085                     _("Error: %s chess program (%s) exited unexpectedly"),
12086                     cps->which, cps->program);
12087         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12088                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12089                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12090                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12091                 } else {
12092                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12093                 }
12094                 gameInfo.resultDetails = buf;
12095             }
12096             RemoveInputSource(cps->isr);
12097             DisplayFatalError(buf, 0, 1);
12098         } else {
12099             sprintf(buf,
12100                     _("Error reading from %s chess program (%s)"),
12101                     cps->which, cps->program);
12102             RemoveInputSource(cps->isr);
12103
12104             /* [AS] Program is misbehaving badly... kill it */
12105             if( count == -2 ) {
12106                 DestroyChildProcess( cps->pr, 9 );
12107                 cps->pr = NoProc;
12108             }
12109
12110             DisplayFatalError(buf, error, 1);
12111         }
12112         return;
12113     }
12114     
12115     if ((end_str = strchr(message, '\r')) != NULL)
12116       *end_str = NULLCHAR;
12117     if ((end_str = strchr(message, '\n')) != NULL)
12118       *end_str = NULLCHAR;
12119     
12120     if (appData.debugMode) {
12121         TimeMark now; int print = 1;
12122         char *quote = ""; char c; int i;
12123
12124         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12125                 char start = message[0];
12126                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12127                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12128                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12129                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12130                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12131                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12132                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12133                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12134                         { quote = "# "; print = (appData.engineComments == 2); }
12135                 message[0] = start; // restore original message
12136         }
12137         if(print) {
12138                 GetTimeMark(&now);
12139                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12140                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12141                         quote,
12142                         message);
12143         }
12144     }
12145
12146     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12147     if (appData.icsEngineAnalyze) {
12148         if (strstr(message, "whisper") != NULL ||
12149              strstr(message, "kibitz") != NULL || 
12150             strstr(message, "tellics") != NULL) return;
12151     }
12152
12153     HandleMachineMove(message, cps);
12154 }
12155
12156
12157 void
12158 SendTimeControl(cps, mps, tc, inc, sd, st)
12159      ChessProgramState *cps;
12160      int mps, inc, sd, st;
12161      long tc;
12162 {
12163     char buf[MSG_SIZ];
12164     int seconds;
12165
12166     if( timeControl_2 > 0 ) {
12167         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12168             tc = timeControl_2;
12169         }
12170     }
12171     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12172     inc /= cps->timeOdds;
12173     st  /= cps->timeOdds;
12174
12175     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12176
12177     if (st > 0) {
12178       /* Set exact time per move, normally using st command */
12179       if (cps->stKludge) {
12180         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12181         seconds = st % 60;
12182         if (seconds == 0) {
12183           sprintf(buf, "level 1 %d\n", st/60);
12184         } else {
12185           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12186         }
12187       } else {
12188         sprintf(buf, "st %d\n", st);
12189       }
12190     } else {
12191       /* Set conventional or incremental time control, using level command */
12192       if (seconds == 0) {
12193         /* Note old gnuchess bug -- minutes:seconds used to not work.
12194            Fixed in later versions, but still avoid :seconds
12195            when seconds is 0. */
12196         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12197       } else {
12198         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12199                 seconds, inc/1000);
12200       }
12201     }
12202     SendToProgram(buf, cps);
12203
12204     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12205     /* Orthogonally, limit search to given depth */
12206     if (sd > 0) {
12207       if (cps->sdKludge) {
12208         sprintf(buf, "depth\n%d\n", sd);
12209       } else {
12210         sprintf(buf, "sd %d\n", sd);
12211       }
12212       SendToProgram(buf, cps);
12213     }
12214
12215     if(cps->nps > 0) { /* [HGM] nps */
12216         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12217         else {
12218                 sprintf(buf, "nps %d\n", cps->nps);
12219               SendToProgram(buf, cps);
12220         }
12221     }
12222 }
12223
12224 ChessProgramState *WhitePlayer()
12225 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12226 {
12227     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12228        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12229         return &second;
12230     return &first;
12231 }
12232
12233 void
12234 SendTimeRemaining(cps, machineWhite)
12235      ChessProgramState *cps;
12236      int /*boolean*/ machineWhite;
12237 {
12238     char message[MSG_SIZ];
12239     long time, otime;
12240
12241     /* Note: this routine must be called when the clocks are stopped
12242        or when they have *just* been set or switched; otherwise
12243        it will be off by the time since the current tick started.
12244     */
12245     if (machineWhite) {
12246         time = whiteTimeRemaining / 10;
12247         otime = blackTimeRemaining / 10;
12248     } else {
12249         time = blackTimeRemaining / 10;
12250         otime = whiteTimeRemaining / 10;
12251     }
12252     /* [HGM] translate opponent's time by time-odds factor */
12253     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12254     if (appData.debugMode) {
12255         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12256     }
12257
12258     if (time <= 0) time = 1;
12259     if (otime <= 0) otime = 1;
12260     
12261     sprintf(message, "time %ld\n", time);
12262     SendToProgram(message, cps);
12263
12264     sprintf(message, "otim %ld\n", otime);
12265     SendToProgram(message, cps);
12266 }
12267
12268 int
12269 BoolFeature(p, name, loc, cps)
12270      char **p;
12271      char *name;
12272      int *loc;
12273      ChessProgramState *cps;
12274 {
12275   char buf[MSG_SIZ];
12276   int len = strlen(name);
12277   int val;
12278   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12279     (*p) += len + 1;
12280     sscanf(*p, "%d", &val);
12281     *loc = (val != 0);
12282     while (**p && **p != ' ') (*p)++;
12283     sprintf(buf, "accepted %s\n", name);
12284     SendToProgram(buf, cps);
12285     return TRUE;
12286   }
12287   return FALSE;
12288 }
12289
12290 int
12291 IntFeature(p, name, loc, cps)
12292      char **p;
12293      char *name;
12294      int *loc;
12295      ChessProgramState *cps;
12296 {
12297   char buf[MSG_SIZ];
12298   int len = strlen(name);
12299   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12300     (*p) += len + 1;
12301     sscanf(*p, "%d", loc);
12302     while (**p && **p != ' ') (*p)++;
12303     sprintf(buf, "accepted %s\n", name);
12304     SendToProgram(buf, cps);
12305     return TRUE;
12306   }
12307   return FALSE;
12308 }
12309
12310 int
12311 StringFeature(p, name, loc, cps)
12312      char **p;
12313      char *name;
12314      char loc[];
12315      ChessProgramState *cps;
12316 {
12317   char buf[MSG_SIZ];
12318   int len = strlen(name);
12319   if (strncmp((*p), name, len) == 0
12320       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12321     (*p) += len + 2;
12322     sscanf(*p, "%[^\"]", loc);
12323     while (**p && **p != '\"') (*p)++;
12324     if (**p == '\"') (*p)++;
12325     sprintf(buf, "accepted %s\n", name);
12326     SendToProgram(buf, cps);
12327     return TRUE;
12328   }
12329   return FALSE;
12330 }
12331
12332 int 
12333 ParseOption(Option *opt, ChessProgramState *cps)
12334 // [HGM] options: process the string that defines an engine option, and determine
12335 // name, type, default value, and allowed value range
12336 {
12337         char *p, *q, buf[MSG_SIZ];
12338         int n, min = (-1)<<31, max = 1<<31, def;
12339
12340         if(p = strstr(opt->name, " -spin ")) {
12341             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12342             if(max < min) max = min; // enforce consistency
12343             if(def < min) def = min;
12344             if(def > max) def = max;
12345             opt->value = def;
12346             opt->min = min;
12347             opt->max = max;
12348             opt->type = Spin;
12349         } else if((p = strstr(opt->name, " -slider "))) {
12350             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12351             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12352             if(max < min) max = min; // enforce consistency
12353             if(def < min) def = min;
12354             if(def > max) def = max;
12355             opt->value = def;
12356             opt->min = min;
12357             opt->max = max;
12358             opt->type = Spin; // Slider;
12359         } else if((p = strstr(opt->name, " -string "))) {
12360             opt->textValue = p+9;
12361             opt->type = TextBox;
12362         } else if((p = strstr(opt->name, " -file "))) {
12363             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12364             opt->textValue = p+7;
12365             opt->type = TextBox; // FileName;
12366         } else if((p = strstr(opt->name, " -path "))) {
12367             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12368             opt->textValue = p+7;
12369             opt->type = TextBox; // PathName;
12370         } else if(p = strstr(opt->name, " -check ")) {
12371             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12372             opt->value = (def != 0);
12373             opt->type = CheckBox;
12374         } else if(p = strstr(opt->name, " -combo ")) {
12375             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12376             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12377             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12378             opt->value = n = 0;
12379             while(q = StrStr(q, " /// ")) {
12380                 n++; *q = 0;    // count choices, and null-terminate each of them
12381                 q += 5;
12382                 if(*q == '*') { // remember default, which is marked with * prefix
12383                     q++;
12384                     opt->value = n;
12385                 }
12386                 cps->comboList[cps->comboCnt++] = q;
12387             }
12388             cps->comboList[cps->comboCnt++] = NULL;
12389             opt->max = n + 1;
12390             opt->type = ComboBox;
12391         } else if(p = strstr(opt->name, " -button")) {
12392             opt->type = Button;
12393         } else if(p = strstr(opt->name, " -save")) {
12394             opt->type = SaveButton;
12395         } else return FALSE;
12396         *p = 0; // terminate option name
12397         // now look if the command-line options define a setting for this engine option.
12398         if(cps->optionSettings && cps->optionSettings[0])
12399             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12400         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12401                 sprintf(buf, "option %s", p);
12402                 if(p = strstr(buf, ",")) *p = 0;
12403                 strcat(buf, "\n");
12404                 SendToProgram(buf, cps);
12405         }
12406         return TRUE;
12407 }
12408
12409 void
12410 FeatureDone(cps, val)
12411      ChessProgramState* cps;
12412      int val;
12413 {
12414   DelayedEventCallback cb = GetDelayedEvent();
12415   if ((cb == InitBackEnd3 && cps == &first) ||
12416       (cb == TwoMachinesEventIfReady && cps == &second)) {
12417     CancelDelayedEvent();
12418     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12419   }
12420   cps->initDone = val;
12421 }
12422
12423 /* Parse feature command from engine */
12424 void
12425 ParseFeatures(args, cps)
12426      char* args;
12427      ChessProgramState *cps;  
12428 {
12429   char *p = args;
12430   char *q;
12431   int val;
12432   char buf[MSG_SIZ];
12433
12434   for (;;) {
12435     while (*p == ' ') p++;
12436     if (*p == NULLCHAR) return;
12437
12438     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12439     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12440     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12441     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12442     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12443     if (BoolFeature(&p, "reuse", &val, cps)) {
12444       /* Engine can disable reuse, but can't enable it if user said no */
12445       if (!val) cps->reuse = FALSE;
12446       continue;
12447     }
12448     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12449     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12450       if (gameMode == TwoMachinesPlay) {
12451         DisplayTwoMachinesTitle();
12452       } else {
12453         DisplayTitle("");
12454       }
12455       continue;
12456     }
12457     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12458     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12459     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12460     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12461     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12462     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12463     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12464     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12465     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12466     if (IntFeature(&p, "done", &val, cps)) {
12467       FeatureDone(cps, val);
12468       continue;
12469     }
12470     /* Added by Tord: */
12471     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12472     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12473     /* End of additions by Tord */
12474
12475     /* [HGM] added features: */
12476     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12477     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12478     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12479     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12480     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12481     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12482     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12483         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12484             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12485             SendToProgram(buf, cps);
12486             continue;
12487         }
12488         if(cps->nrOptions >= MAX_OPTIONS) {
12489             cps->nrOptions--;
12490             sprintf(buf, "%s engine has too many options\n", cps->which);
12491             DisplayError(buf, 0);
12492         }
12493         continue;
12494     }
12495     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12496     /* End of additions by HGM */
12497
12498     /* unknown feature: complain and skip */
12499     q = p;
12500     while (*q && *q != '=') q++;
12501     sprintf(buf, "rejected %.*s\n", q-p, p);
12502     SendToProgram(buf, cps);
12503     p = q;
12504     if (*p == '=') {
12505       p++;
12506       if (*p == '\"') {
12507         p++;
12508         while (*p && *p != '\"') p++;
12509         if (*p == '\"') p++;
12510       } else {
12511         while (*p && *p != ' ') p++;
12512       }
12513     }
12514   }
12515
12516 }
12517
12518 void
12519 PeriodicUpdatesEvent(newState)
12520      int newState;
12521 {
12522     if (newState == appData.periodicUpdates)
12523       return;
12524
12525     appData.periodicUpdates=newState;
12526
12527     /* Display type changes, so update it now */
12528     DisplayAnalysis();
12529
12530     /* Get the ball rolling again... */
12531     if (newState) {
12532         AnalysisPeriodicEvent(1);
12533         StartAnalysisClock();
12534     }
12535 }
12536
12537 void
12538 PonderNextMoveEvent(newState)
12539      int newState;
12540 {
12541     if (newState == appData.ponderNextMove) return;
12542     if (gameMode == EditPosition) EditPositionDone();
12543     if (newState) {
12544         SendToProgram("hard\n", &first);
12545         if (gameMode == TwoMachinesPlay) {
12546             SendToProgram("hard\n", &second);
12547         }
12548     } else {
12549         SendToProgram("easy\n", &first);
12550         thinkOutput[0] = NULLCHAR;
12551         if (gameMode == TwoMachinesPlay) {
12552             SendToProgram("easy\n", &second);
12553         }
12554     }
12555     appData.ponderNextMove = newState;
12556 }
12557
12558 void
12559 NewSettingEvent(option, command, value)
12560      char *command;
12561      int option, value;
12562 {
12563     char buf[MSG_SIZ];
12564
12565     if (gameMode == EditPosition) EditPositionDone();
12566     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12567     SendToProgram(buf, &first);
12568     if (gameMode == TwoMachinesPlay) {
12569         SendToProgram(buf, &second);
12570     }
12571 }
12572
12573 void
12574 ShowThinkingEvent()
12575 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12576 {
12577     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12578     int newState = appData.showThinking
12579         // [HGM] thinking: other features now need thinking output as well
12580         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12581     
12582     if (oldState == newState) return;
12583     oldState = newState;
12584     if (gameMode == EditPosition) EditPositionDone();
12585     if (oldState) {
12586         SendToProgram("post\n", &first);
12587         if (gameMode == TwoMachinesPlay) {
12588             SendToProgram("post\n", &second);
12589         }
12590     } else {
12591         SendToProgram("nopost\n", &first);
12592         thinkOutput[0] = NULLCHAR;
12593         if (gameMode == TwoMachinesPlay) {
12594             SendToProgram("nopost\n", &second);
12595         }
12596     }
12597 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12598 }
12599
12600 void
12601 AskQuestionEvent(title, question, replyPrefix, which)
12602      char *title; char *question; char *replyPrefix; char *which;
12603 {
12604   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12605   if (pr == NoProc) return;
12606   AskQuestion(title, question, replyPrefix, pr);
12607 }
12608
12609 void
12610 DisplayMove(moveNumber)
12611      int moveNumber;
12612 {
12613     char message[MSG_SIZ];
12614     char res[MSG_SIZ];
12615     char cpThinkOutput[MSG_SIZ];
12616
12617     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12618     
12619     if (moveNumber == forwardMostMove - 1 || 
12620         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12621
12622         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12623
12624         if (strchr(cpThinkOutput, '\n')) {
12625             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12626         }
12627     } else {
12628         *cpThinkOutput = NULLCHAR;
12629     }
12630
12631     /* [AS] Hide thinking from human user */
12632     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12633         *cpThinkOutput = NULLCHAR;
12634         if( thinkOutput[0] != NULLCHAR ) {
12635             int i;
12636
12637             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12638                 cpThinkOutput[i] = '.';
12639             }
12640             cpThinkOutput[i] = NULLCHAR;
12641             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12642         }
12643     }
12644
12645     if (moveNumber == forwardMostMove - 1 &&
12646         gameInfo.resultDetails != NULL) {
12647         if (gameInfo.resultDetails[0] == NULLCHAR) {
12648             sprintf(res, " %s", PGNResult(gameInfo.result));
12649         } else {
12650             sprintf(res, " {%s} %s",
12651                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12652         }
12653     } else {
12654         res[0] = NULLCHAR;
12655     }
12656
12657     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12658         DisplayMessage(res, cpThinkOutput);
12659     } else {
12660         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12661                 WhiteOnMove(moveNumber) ? " " : ".. ",
12662                 parseList[moveNumber], res);
12663         DisplayMessage(message, cpThinkOutput);
12664     }
12665 }
12666
12667 void
12668 DisplayAnalysisText(text)
12669      char *text;
12670 {
12671     char buf[MSG_SIZ];
12672
12673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12674                || appData.icsEngineAnalyze) {
12675         sprintf(buf, "Analysis (%s)", first.tidy);
12676         AnalysisPopUp(buf, text);
12677     }
12678 }
12679
12680 static int
12681 only_one_move(str)
12682      char *str;
12683 {
12684     while (*str && isspace(*str)) ++str;
12685     while (*str && !isspace(*str)) ++str;
12686     if (!*str) return 1;
12687     while (*str && isspace(*str)) ++str;
12688     if (!*str) return 1;
12689     return 0;
12690 }
12691
12692 void
12693 DisplayAnalysis()
12694 {
12695     char buf[MSG_SIZ];
12696     char lst[MSG_SIZ / 2];
12697     double nps;
12698     static char *xtra[] = { "", " (--)", " (++)" };
12699     int h, m, s, cs;
12700   
12701     if (programStats.time == 0) {
12702         programStats.time = 1;
12703     }
12704   
12705     if (programStats.got_only_move) {
12706         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12707     } else {
12708         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12709
12710         nps = (u64ToDouble(programStats.nodes) /
12711              ((double)programStats.time /100.0));
12712
12713         cs = programStats.time % 100;
12714         s = programStats.time / 100;
12715         h = (s / (60*60));
12716         s = s - h*60*60;
12717         m = (s/60);
12718         s = s - m*60;
12719
12720         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12721           if (programStats.move_name[0] != NULLCHAR) {
12722             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12723                     programStats.depth,
12724                     programStats.nr_moves-programStats.moves_left,
12725                     programStats.nr_moves, programStats.move_name,
12726                     ((float)programStats.score)/100.0, lst,
12727                     only_one_move(lst)?
12728                     xtra[programStats.got_fail] : "",
12729                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12730           } else {
12731             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12732                     programStats.depth,
12733                     programStats.nr_moves-programStats.moves_left,
12734                     programStats.nr_moves, ((float)programStats.score)/100.0,
12735                     lst,
12736                     only_one_move(lst)?
12737                     xtra[programStats.got_fail] : "",
12738                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12739           }
12740         } else {
12741             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12742                     programStats.depth,
12743                     ((float)programStats.score)/100.0,
12744                     lst,
12745                     only_one_move(lst)?
12746                     xtra[programStats.got_fail] : "",
12747                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12748         }
12749     }
12750     DisplayAnalysisText(buf);
12751 }
12752
12753 void
12754 DisplayComment(moveNumber, text)
12755      int moveNumber;
12756      char *text;
12757 {
12758     char title[MSG_SIZ];
12759     char buf[8000]; // comment can be long!
12760     int score, depth;
12761
12762     if( appData.autoDisplayComment ) {
12763         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12764             strcpy(title, "Comment");
12765         } else {
12766             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12767                     WhiteOnMove(moveNumber) ? " " : ".. ",
12768                     parseList[moveNumber]);
12769         }
12770         // [HGM] PV info: display PV info together with (or as) comment
12771         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12772             if(text == NULL) text = "";                                           
12773             score = pvInfoList[moveNumber].score;
12774             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12775                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12776             text = buf;
12777         }
12778     } else title[0] = 0;
12779
12780     if (text != NULL)
12781         CommentPopUp(title, text);
12782 }
12783
12784 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12785  * might be busy thinking or pondering.  It can be omitted if your
12786  * gnuchess is configured to stop thinking immediately on any user
12787  * input.  However, that gnuchess feature depends on the FIONREAD
12788  * ioctl, which does not work properly on some flavors of Unix.
12789  */
12790 void
12791 Attention(cps)
12792      ChessProgramState *cps;
12793 {
12794 #if ATTENTION
12795     if (!cps->useSigint) return;
12796     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12797     switch (gameMode) {
12798       case MachinePlaysWhite:
12799       case MachinePlaysBlack:
12800       case TwoMachinesPlay:
12801       case IcsPlayingWhite:
12802       case IcsPlayingBlack:
12803       case AnalyzeMode:
12804       case AnalyzeFile:
12805         /* Skip if we know it isn't thinking */
12806         if (!cps->maybeThinking) return;
12807         if (appData.debugMode)
12808           fprintf(debugFP, "Interrupting %s\n", cps->which);
12809         InterruptChildProcess(cps->pr);
12810         cps->maybeThinking = FALSE;
12811         break;
12812       default:
12813         break;
12814     }
12815 #endif /*ATTENTION*/
12816 }
12817
12818 int
12819 CheckFlags()
12820 {
12821     if (whiteTimeRemaining <= 0) {
12822         if (!whiteFlag) {
12823             whiteFlag = TRUE;
12824             if (appData.icsActive) {
12825                 if (appData.autoCallFlag &&
12826                     gameMode == IcsPlayingBlack && !blackFlag) {
12827                   SendToICS(ics_prefix);
12828                   SendToICS("flag\n");
12829                 }
12830             } else {
12831                 if (blackFlag) {
12832                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12833                 } else {
12834                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12835                     if (appData.autoCallFlag) {
12836                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12837                         return TRUE;
12838                     }
12839                 }
12840             }
12841         }
12842     }
12843     if (blackTimeRemaining <= 0) {
12844         if (!blackFlag) {
12845             blackFlag = TRUE;
12846             if (appData.icsActive) {
12847                 if (appData.autoCallFlag &&
12848                     gameMode == IcsPlayingWhite && !whiteFlag) {
12849                   SendToICS(ics_prefix);
12850                   SendToICS("flag\n");
12851                 }
12852             } else {
12853                 if (whiteFlag) {
12854                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12855                 } else {
12856                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12857                     if (appData.autoCallFlag) {
12858                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12859                         return TRUE;
12860                     }
12861                 }
12862             }
12863         }
12864     }
12865     return FALSE;
12866 }
12867
12868 void
12869 CheckTimeControl()
12870 {
12871     if (!appData.clockMode || appData.icsActive ||
12872         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12873
12874     /*
12875      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12876      */
12877     if ( !WhiteOnMove(forwardMostMove) )
12878         /* White made time control */
12879         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12880         /* [HGM] time odds: correct new time quota for time odds! */
12881                                             / WhitePlayer()->timeOdds;
12882       else
12883         /* Black made time control */
12884         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12885                                             / WhitePlayer()->other->timeOdds;
12886 }
12887
12888 void
12889 DisplayBothClocks()
12890 {
12891     int wom = gameMode == EditPosition ?
12892       !blackPlaysFirst : WhiteOnMove(currentMove);
12893     DisplayWhiteClock(whiteTimeRemaining, wom);
12894     DisplayBlackClock(blackTimeRemaining, !wom);
12895 }
12896
12897
12898 /* Timekeeping seems to be a portability nightmare.  I think everyone
12899    has ftime(), but I'm really not sure, so I'm including some ifdefs
12900    to use other calls if you don't.  Clocks will be less accurate if
12901    you have neither ftime nor gettimeofday.
12902 */
12903
12904 /* VS 2008 requires the #include outside of the function */
12905 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12906 #include <sys/timeb.h>
12907 #endif
12908
12909 /* Get the current time as a TimeMark */
12910 void
12911 GetTimeMark(tm)
12912      TimeMark *tm;
12913 {
12914 #if HAVE_GETTIMEOFDAY
12915
12916     struct timeval timeVal;
12917     struct timezone timeZone;
12918
12919     gettimeofday(&timeVal, &timeZone);
12920     tm->sec = (long) timeVal.tv_sec; 
12921     tm->ms = (int) (timeVal.tv_usec / 1000L);
12922
12923 #else /*!HAVE_GETTIMEOFDAY*/
12924 #if HAVE_FTIME
12925
12926 // include <sys/timeb.h> / moved to just above start of function
12927     struct timeb timeB;
12928
12929     ftime(&timeB);
12930     tm->sec = (long) timeB.time;
12931     tm->ms = (int) timeB.millitm;
12932
12933 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12934     tm->sec = (long) time(NULL);
12935     tm->ms = 0;
12936 #endif
12937 #endif
12938 }
12939
12940 /* Return the difference in milliseconds between two
12941    time marks.  We assume the difference will fit in a long!
12942 */
12943 long
12944 SubtractTimeMarks(tm2, tm1)
12945      TimeMark *tm2, *tm1;
12946 {
12947     return 1000L*(tm2->sec - tm1->sec) +
12948            (long) (tm2->ms - tm1->ms);
12949 }
12950
12951
12952 /*
12953  * Code to manage the game clocks.
12954  *
12955  * In tournament play, black starts the clock and then white makes a move.
12956  * We give the human user a slight advantage if he is playing white---the
12957  * clocks don't run until he makes his first move, so it takes zero time.
12958  * Also, we don't account for network lag, so we could get out of sync
12959  * with GNU Chess's clock -- but then, referees are always right.  
12960  */
12961
12962 static TimeMark tickStartTM;
12963 static long intendedTickLength;
12964
12965 long
12966 NextTickLength(timeRemaining)
12967      long timeRemaining;
12968 {
12969     long nominalTickLength, nextTickLength;
12970
12971     if (timeRemaining > 0L && timeRemaining <= 10000L)
12972       nominalTickLength = 100L;
12973     else
12974       nominalTickLength = 1000L;
12975     nextTickLength = timeRemaining % nominalTickLength;
12976     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12977
12978     return nextTickLength;
12979 }
12980
12981 /* Adjust clock one minute up or down */
12982 void
12983 AdjustClock(Boolean which, int dir)
12984 {
12985     if(which) blackTimeRemaining += 60000*dir;
12986     else      whiteTimeRemaining += 60000*dir;
12987     DisplayBothClocks();
12988 }
12989
12990 /* Stop clocks and reset to a fresh time control */
12991 void
12992 ResetClocks() 
12993 {
12994     (void) StopClockTimer();
12995     if (appData.icsActive) {
12996         whiteTimeRemaining = blackTimeRemaining = 0;
12997     } else { /* [HGM] correct new time quote for time odds */
12998         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12999         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13000     }
13001     if (whiteFlag || blackFlag) {
13002         DisplayTitle("");
13003         whiteFlag = blackFlag = FALSE;
13004     }
13005     DisplayBothClocks();
13006 }
13007
13008 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13009
13010 /* Decrement running clock by amount of time that has passed */
13011 void
13012 DecrementClocks()
13013 {
13014     long timeRemaining;
13015     long lastTickLength, fudge;
13016     TimeMark now;
13017
13018     if (!appData.clockMode) return;
13019     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13020         
13021     GetTimeMark(&now);
13022
13023     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13024
13025     /* Fudge if we woke up a little too soon */
13026     fudge = intendedTickLength - lastTickLength;
13027     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13028
13029     if (WhiteOnMove(forwardMostMove)) {
13030         if(whiteNPS >= 0) lastTickLength = 0;
13031         timeRemaining = whiteTimeRemaining -= lastTickLength;
13032         DisplayWhiteClock(whiteTimeRemaining - fudge,
13033                           WhiteOnMove(currentMove));
13034     } else {
13035         if(blackNPS >= 0) lastTickLength = 0;
13036         timeRemaining = blackTimeRemaining -= lastTickLength;
13037         DisplayBlackClock(blackTimeRemaining - fudge,
13038                           !WhiteOnMove(currentMove));
13039     }
13040
13041     if (CheckFlags()) return;
13042         
13043     tickStartTM = now;
13044     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13045     StartClockTimer(intendedTickLength);
13046
13047     /* if the time remaining has fallen below the alarm threshold, sound the
13048      * alarm. if the alarm has sounded and (due to a takeback or time control
13049      * with increment) the time remaining has increased to a level above the
13050      * threshold, reset the alarm so it can sound again. 
13051      */
13052     
13053     if (appData.icsActive && appData.icsAlarm) {
13054
13055         /* make sure we are dealing with the user's clock */
13056         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13057                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13058            )) return;
13059
13060         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13061             alarmSounded = FALSE;
13062         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13063             PlayAlarmSound();
13064             alarmSounded = TRUE;
13065         }
13066     }
13067 }
13068
13069
13070 /* A player has just moved, so stop the previously running
13071    clock and (if in clock mode) start the other one.
13072    We redisplay both clocks in case we're in ICS mode, because
13073    ICS gives us an update to both clocks after every move.
13074    Note that this routine is called *after* forwardMostMove
13075    is updated, so the last fractional tick must be subtracted
13076    from the color that is *not* on move now.
13077 */
13078 void
13079 SwitchClocks()
13080 {
13081     long lastTickLength;
13082     TimeMark now;
13083     int flagged = FALSE;
13084
13085     GetTimeMark(&now);
13086
13087     if (StopClockTimer() && appData.clockMode) {
13088         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13089         if (WhiteOnMove(forwardMostMove)) {
13090             if(blackNPS >= 0) lastTickLength = 0;
13091             blackTimeRemaining -= lastTickLength;
13092            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13093 //         if(pvInfoList[forwardMostMove-1].time == -1)
13094                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13095                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13096         } else {
13097            if(whiteNPS >= 0) lastTickLength = 0;
13098            whiteTimeRemaining -= lastTickLength;
13099            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13100 //         if(pvInfoList[forwardMostMove-1].time == -1)
13101                  pvInfoList[forwardMostMove-1].time = 
13102                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13103         }
13104         flagged = CheckFlags();
13105     }
13106     CheckTimeControl();
13107
13108     if (flagged || !appData.clockMode) return;
13109
13110     switch (gameMode) {
13111       case MachinePlaysBlack:
13112       case MachinePlaysWhite:
13113       case BeginningOfGame:
13114         if (pausing) return;
13115         break;
13116
13117       case EditGame:
13118       case PlayFromGameFile:
13119       case IcsExamining:
13120         return;
13121
13122       default:
13123         break;
13124     }
13125
13126     tickStartTM = now;
13127     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13128       whiteTimeRemaining : blackTimeRemaining);
13129     StartClockTimer(intendedTickLength);
13130 }
13131         
13132
13133 /* Stop both clocks */
13134 void
13135 StopClocks()
13136 {       
13137     long lastTickLength;
13138     TimeMark now;
13139
13140     if (!StopClockTimer()) return;
13141     if (!appData.clockMode) return;
13142
13143     GetTimeMark(&now);
13144
13145     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13146     if (WhiteOnMove(forwardMostMove)) {
13147         if(whiteNPS >= 0) lastTickLength = 0;
13148         whiteTimeRemaining -= lastTickLength;
13149         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13150     } else {
13151         if(blackNPS >= 0) lastTickLength = 0;
13152         blackTimeRemaining -= lastTickLength;
13153         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13154     }
13155     CheckFlags();
13156 }
13157         
13158 /* Start clock of player on move.  Time may have been reset, so
13159    if clock is already running, stop and restart it. */
13160 void
13161 StartClocks()
13162 {
13163     (void) StopClockTimer(); /* in case it was running already */
13164     DisplayBothClocks();
13165     if (CheckFlags()) return;
13166
13167     if (!appData.clockMode) return;
13168     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13169
13170     GetTimeMark(&tickStartTM);
13171     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13172       whiteTimeRemaining : blackTimeRemaining);
13173
13174    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13175     whiteNPS = blackNPS = -1; 
13176     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13177        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13178         whiteNPS = first.nps;
13179     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13180        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13181         blackNPS = first.nps;
13182     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13183         whiteNPS = second.nps;
13184     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13185         blackNPS = second.nps;
13186     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13187
13188     StartClockTimer(intendedTickLength);
13189 }
13190
13191 char *
13192 TimeString(ms)
13193      long ms;
13194 {
13195     long second, minute, hour, day;
13196     char *sign = "";
13197     static char buf[32];
13198     
13199     if (ms > 0 && ms <= 9900) {
13200       /* convert milliseconds to tenths, rounding up */
13201       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13202
13203       sprintf(buf, " %03.1f ", tenths/10.0);
13204       return buf;
13205     }
13206
13207     /* convert milliseconds to seconds, rounding up */
13208     /* use floating point to avoid strangeness of integer division
13209        with negative dividends on many machines */
13210     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13211
13212     if (second < 0) {
13213         sign = "-";
13214         second = -second;
13215     }
13216     
13217     day = second / (60 * 60 * 24);
13218     second = second % (60 * 60 * 24);
13219     hour = second / (60 * 60);
13220     second = second % (60 * 60);
13221     minute = second / 60;
13222     second = second % 60;
13223     
13224     if (day > 0)
13225       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13226               sign, day, hour, minute, second);
13227     else if (hour > 0)
13228       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13229     else
13230       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13231     
13232     return buf;
13233 }
13234
13235
13236 /*
13237  * This is necessary because some C libraries aren't ANSI C compliant yet.
13238  */
13239 char *
13240 StrStr(string, match)
13241      char *string, *match;
13242 {
13243     int i, length;
13244     
13245     length = strlen(match);
13246     
13247     for (i = strlen(string) - length; i >= 0; i--, string++)
13248       if (!strncmp(match, string, length))
13249         return string;
13250     
13251     return NULL;
13252 }
13253
13254 char *
13255 StrCaseStr(string, match)
13256      char *string, *match;
13257 {
13258     int i, j, length;
13259     
13260     length = strlen(match);
13261     
13262     for (i = strlen(string) - length; i >= 0; i--, string++) {
13263         for (j = 0; j < length; j++) {
13264             if (ToLower(match[j]) != ToLower(string[j]))
13265               break;
13266         }
13267         if (j == length) return string;
13268     }
13269
13270     return NULL;
13271 }
13272
13273 #ifndef _amigados
13274 int
13275 StrCaseCmp(s1, s2)
13276      char *s1, *s2;
13277 {
13278     char c1, c2;
13279     
13280     for (;;) {
13281         c1 = ToLower(*s1++);
13282         c2 = ToLower(*s2++);
13283         if (c1 > c2) return 1;
13284         if (c1 < c2) return -1;
13285         if (c1 == NULLCHAR) return 0;
13286     }
13287 }
13288
13289
13290 int
13291 ToLower(c)
13292      int c;
13293 {
13294     return isupper(c) ? tolower(c) : c;
13295 }
13296
13297
13298 int
13299 ToUpper(c)
13300      int c;
13301 {
13302     return islower(c) ? toupper(c) : c;
13303 }
13304 #endif /* !_amigados    */
13305
13306 char *
13307 StrSave(s)
13308      char *s;
13309 {
13310     char *ret;
13311
13312     if ((ret = (char *) malloc(strlen(s) + 1))) {
13313         strcpy(ret, s);
13314     }
13315     return ret;
13316 }
13317
13318 char *
13319 StrSavePtr(s, savePtr)
13320      char *s, **savePtr;
13321 {
13322     if (*savePtr) {
13323         free(*savePtr);
13324     }
13325     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13326         strcpy(*savePtr, s);
13327     }
13328     return(*savePtr);
13329 }
13330
13331 char *
13332 PGNDate()
13333 {
13334     time_t clock;
13335     struct tm *tm;
13336     char buf[MSG_SIZ];
13337
13338     clock = time((time_t *)NULL);
13339     tm = localtime(&clock);
13340     sprintf(buf, "%04d.%02d.%02d",
13341             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13342     return StrSave(buf);
13343 }
13344
13345
13346 char *
13347 PositionToFEN(move, overrideCastling)
13348      int move;
13349      char *overrideCastling;
13350 {
13351     int i, j, fromX, fromY, toX, toY;
13352     int whiteToPlay;
13353     char buf[128];
13354     char *p, *q;
13355     int emptycount;
13356     ChessSquare piece;
13357
13358     whiteToPlay = (gameMode == EditPosition) ?
13359       !blackPlaysFirst : (move % 2 == 0);
13360     p = buf;
13361
13362     /* Piece placement data */
13363     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13364         emptycount = 0;
13365         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13366             if (boards[move][i][j] == EmptySquare) {
13367                 emptycount++;
13368             } else { ChessSquare piece = boards[move][i][j];
13369                 if (emptycount > 0) {
13370                     if(emptycount<10) /* [HGM] can be >= 10 */
13371                         *p++ = '0' + emptycount;
13372                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13373                     emptycount = 0;
13374                 }
13375                 if(PieceToChar(piece) == '+') {
13376                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13377                     *p++ = '+';
13378                     piece = (ChessSquare)(DEMOTED piece);
13379                 } 
13380                 *p++ = PieceToChar(piece);
13381                 if(p[-1] == '~') {
13382                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13383                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13384                     *p++ = '~';
13385                 }
13386             }
13387         }
13388         if (emptycount > 0) {
13389             if(emptycount<10) /* [HGM] can be >= 10 */
13390                 *p++ = '0' + emptycount;
13391             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13392             emptycount = 0;
13393         }
13394         *p++ = '/';
13395     }
13396     *(p - 1) = ' ';
13397
13398     /* [HGM] print Crazyhouse or Shogi holdings */
13399     if( gameInfo.holdingsWidth ) {
13400         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13401         q = p;
13402         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13403             piece = boards[move][i][BOARD_WIDTH-1];
13404             if( piece != EmptySquare )
13405               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13406                   *p++ = PieceToChar(piece);
13407         }
13408         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13409             piece = boards[move][BOARD_HEIGHT-i-1][0];
13410             if( piece != EmptySquare )
13411               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13412                   *p++ = PieceToChar(piece);
13413         }
13414
13415         if( q == p ) *p++ = '-';
13416         *p++ = ']';
13417         *p++ = ' ';
13418     }
13419
13420     /* Active color */
13421     *p++ = whiteToPlay ? 'w' : 'b';
13422     *p++ = ' ';
13423
13424   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13425     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13426   } else {
13427   if(nrCastlingRights) {
13428      q = p;
13429      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13430        /* [HGM] write directly from rights */
13431            if(castlingRights[move][2] >= 0 &&
13432               castlingRights[move][0] >= 0   )
13433                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13434            if(castlingRights[move][2] >= 0 &&
13435               castlingRights[move][1] >= 0   )
13436                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13437            if(castlingRights[move][5] >= 0 &&
13438               castlingRights[move][3] >= 0   )
13439                 *p++ = castlingRights[move][3] + AAA;
13440            if(castlingRights[move][5] >= 0 &&
13441               castlingRights[move][4] >= 0   )
13442                 *p++ = castlingRights[move][4] + AAA;
13443      } else {
13444
13445         /* [HGM] write true castling rights */
13446         if( nrCastlingRights == 6 ) {
13447             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13448                castlingRights[move][2] >= 0  ) *p++ = 'K';
13449             if(castlingRights[move][1] == BOARD_LEFT &&
13450                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13451             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13452                castlingRights[move][5] >= 0  ) *p++ = 'k';
13453             if(castlingRights[move][4] == BOARD_LEFT &&
13454                castlingRights[move][5] >= 0  ) *p++ = 'q';
13455         }
13456      }
13457      if (q == p) *p++ = '-'; /* No castling rights */
13458      *p++ = ' ';
13459   }
13460
13461   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13462      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13463     /* En passant target square */
13464     if (move > backwardMostMove) {
13465         fromX = moveList[move - 1][0] - AAA;
13466         fromY = moveList[move - 1][1] - ONE;
13467         toX = moveList[move - 1][2] - AAA;
13468         toY = moveList[move - 1][3] - ONE;
13469         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13470             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13471             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13472             fromX == toX) {
13473             /* 2-square pawn move just happened */
13474             *p++ = toX + AAA;
13475             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13476         } else {
13477             *p++ = '-';
13478         }
13479     } else {
13480         *p++ = '-';
13481     }
13482     *p++ = ' ';
13483   }
13484   }
13485
13486     /* [HGM] find reversible plies */
13487     {   int i = 0, j=move;
13488
13489         if (appData.debugMode) { int k;
13490             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13491             for(k=backwardMostMove; k<=forwardMostMove; k++)
13492                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13493
13494         }
13495
13496         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13497         if( j == backwardMostMove ) i += initialRulePlies;
13498         sprintf(p, "%d ", i);
13499         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13500     }
13501     /* Fullmove number */
13502     sprintf(p, "%d", (move / 2) + 1);
13503     
13504     return StrSave(buf);
13505 }
13506
13507 Boolean
13508 ParseFEN(board, blackPlaysFirst, fen)
13509     Board board;
13510      int *blackPlaysFirst;
13511      char *fen;
13512 {
13513     int i, j;
13514     char *p;
13515     int emptycount;
13516     ChessSquare piece;
13517
13518     p = fen;
13519
13520     /* [HGM] by default clear Crazyhouse holdings, if present */
13521     if(gameInfo.holdingsWidth) {
13522        for(i=0; i<BOARD_HEIGHT; i++) {
13523            board[i][0]             = EmptySquare; /* black holdings */
13524            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13525            board[i][1]             = (ChessSquare) 0; /* black counts */
13526            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13527        }
13528     }
13529
13530     /* Piece placement data */
13531     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13532         j = 0;
13533         for (;;) {
13534             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13535                 if (*p == '/') p++;
13536                 emptycount = gameInfo.boardWidth - j;
13537                 while (emptycount--)
13538                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13539                 break;
13540 #if(BOARD_SIZE >= 10)
13541             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13542                 p++; emptycount=10;
13543                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13544                 while (emptycount--)
13545                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13546 #endif
13547             } else if (isdigit(*p)) {
13548                 emptycount = *p++ - '0';
13549                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13550                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13551                 while (emptycount--)
13552                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13553             } else if (*p == '+' || isalpha(*p)) {
13554                 if (j >= gameInfo.boardWidth) return FALSE;
13555                 if(*p=='+') {
13556                     piece = CharToPiece(*++p);
13557                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13558                     piece = (ChessSquare) (PROMOTED piece ); p++;
13559                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13560                 } else piece = CharToPiece(*p++);
13561
13562                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13563                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13564                     piece = (ChessSquare) (PROMOTED piece);
13565                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13566                     p++;
13567                 }
13568                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13569             } else {
13570                 return FALSE;
13571             }
13572         }
13573     }
13574     while (*p == '/' || *p == ' ') p++;
13575
13576     /* [HGM] look for Crazyhouse holdings here */
13577     while(*p==' ') p++;
13578     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13579         if(*p == '[') p++;
13580         if(*p == '-' ) *p++; /* empty holdings */ else {
13581             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13582             /* if we would allow FEN reading to set board size, we would   */
13583             /* have to add holdings and shift the board read so far here   */
13584             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13585                 *p++;
13586                 if((int) piece >= (int) BlackPawn ) {
13587                     i = (int)piece - (int)BlackPawn;
13588                     i = PieceToNumber((ChessSquare)i);
13589                     if( i >= gameInfo.holdingsSize ) return FALSE;
13590                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13591                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13592                 } else {
13593                     i = (int)piece - (int)WhitePawn;
13594                     i = PieceToNumber((ChessSquare)i);
13595                     if( i >= gameInfo.holdingsSize ) return FALSE;
13596                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13597                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13598                 }
13599             }
13600         }
13601         if(*p == ']') *p++;
13602     }
13603
13604     while(*p == ' ') p++;
13605
13606     /* Active color */
13607     switch (*p++) {
13608       case 'w':
13609         *blackPlaysFirst = FALSE;
13610         break;
13611       case 'b': 
13612         *blackPlaysFirst = TRUE;
13613         break;
13614       default:
13615         return FALSE;
13616     }
13617
13618     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13619     /* return the extra info in global variiables             */
13620
13621     /* set defaults in case FEN is incomplete */
13622     FENepStatus = EP_UNKNOWN;
13623     for(i=0; i<nrCastlingRights; i++ ) {
13624         FENcastlingRights[i] =
13625             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13626     }   /* assume possible unless obviously impossible */
13627     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13628     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13629     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13630     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13631     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13632     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13633     FENrulePlies = 0;
13634
13635     while(*p==' ') p++;
13636     if(nrCastlingRights) {
13637       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13638           /* castling indicator present, so default becomes no castlings */
13639           for(i=0; i<nrCastlingRights; i++ ) {
13640                  FENcastlingRights[i] = -1;
13641           }
13642       }
13643       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13644              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13645              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13646              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13647         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13648
13649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13650             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13651             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13652         }
13653         switch(c) {
13654           case'K':
13655               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13656               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13657               FENcastlingRights[2] = whiteKingFile;
13658               break;
13659           case'Q':
13660               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13661               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13662               FENcastlingRights[2] = whiteKingFile;
13663               break;
13664           case'k':
13665               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13666               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13667               FENcastlingRights[5] = blackKingFile;
13668               break;
13669           case'q':
13670               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13671               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13672               FENcastlingRights[5] = blackKingFile;
13673           case '-':
13674               break;
13675           default: /* FRC castlings */
13676               if(c >= 'a') { /* black rights */
13677                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13678                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13679                   if(i == BOARD_RGHT) break;
13680                   FENcastlingRights[5] = i;
13681                   c -= AAA;
13682                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13683                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13684                   if(c > i)
13685                       FENcastlingRights[3] = c;
13686                   else
13687                       FENcastlingRights[4] = c;
13688               } else { /* white rights */
13689                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13690                     if(board[0][i] == WhiteKing) break;
13691                   if(i == BOARD_RGHT) break;
13692                   FENcastlingRights[2] = i;
13693                   c -= AAA - 'a' + 'A';
13694                   if(board[0][c] >= WhiteKing) break;
13695                   if(c > i)
13696                       FENcastlingRights[0] = c;
13697                   else
13698                       FENcastlingRights[1] = c;
13699               }
13700         }
13701       }
13702     if (appData.debugMode) {
13703         fprintf(debugFP, "FEN castling rights:");
13704         for(i=0; i<nrCastlingRights; i++)
13705         fprintf(debugFP, " %d", FENcastlingRights[i]);
13706         fprintf(debugFP, "\n");
13707     }
13708
13709       while(*p==' ') p++;
13710     }
13711
13712     /* read e.p. field in games that know e.p. capture */
13713     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13714        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13715       if(*p=='-') {
13716         p++; FENepStatus = EP_NONE;
13717       } else {
13718          char c = *p++ - AAA;
13719
13720          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13721          if(*p >= '0' && *p <='9') *p++;
13722          FENepStatus = c;
13723       }
13724     }
13725
13726
13727     if(sscanf(p, "%d", &i) == 1) {
13728         FENrulePlies = i; /* 50-move ply counter */
13729         /* (The move number is still ignored)    */
13730     }
13731
13732     return TRUE;
13733 }
13734       
13735 void
13736 EditPositionPasteFEN(char *fen)
13737 {
13738   if (fen != NULL) {
13739     Board initial_position;
13740
13741     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13742       DisplayError(_("Bad FEN position in clipboard"), 0);
13743       return ;
13744     } else {
13745       int savedBlackPlaysFirst = blackPlaysFirst;
13746       EditPositionEvent();
13747       blackPlaysFirst = savedBlackPlaysFirst;
13748       CopyBoard(boards[0], initial_position);
13749           /* [HGM] copy FEN attributes as well */
13750           {   int i;
13751               initialRulePlies = FENrulePlies;
13752               epStatus[0] = FENepStatus;
13753               for( i=0; i<nrCastlingRights; i++ )
13754                   castlingRights[0][i] = FENcastlingRights[i];
13755           }
13756       EditPositionDone();
13757       DisplayBothClocks();
13758       DrawPosition(FALSE, boards[currentMove]);
13759     }
13760   }
13761 }