temporary fix for pre-select
[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 InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
234
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
240 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245
246 /* States for ics_getting_history */
247 #define H_FALSE 0
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
253
254 /* whosays values for GameEnds */
255 #define GE_ICS 0
256 #define GE_ENGINE 1
257 #define GE_PLAYER 2
258 #define GE_FILE 3
259 #define GE_XBOARD 4
260 #define GE_ENGINE1 5
261 #define GE_ENGINE2 6
262
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
265
266 /* Different types of move when calling RegisterMove */
267 #define CMAIL_MOVE   0
268 #define CMAIL_RESIGN 1
269 #define CMAIL_DRAW   2
270 #define CMAIL_ACCEPT 3
271
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
276
277 /* Telnet protocol constants */
278 #define TN_WILL 0373
279 #define TN_WONT 0374
280 #define TN_DO   0375
281 #define TN_DONT 0376
282 #define TN_IAC  0377
283 #define TN_ECHO 0001
284 #define TN_SGA  0003
285 #define TN_PORT 23
286
287 /* [AS] */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
289 {
290     assert( dst != NULL );
291     assert( src != NULL );
292     assert( count > 0 );
293
294     strncpy( dst, src, count );
295     dst[ count-1 ] = '\0';
296     return dst;
297 }
298
299 #if 0
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
302 {
303     size_t  dst_len;
304
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     dst_len = strlen(dst);
310
311     assert( count > dst_len ); /* Buffer size must be greater than current length */
312
313     safeStrCpy( dst + dst_len, src, count - dst_len );
314
315     return dst;
316 }
317 #endif
318
319 /* Some compiler can't cast u64 to double
320  * This function do the job for us:
321
322  * We use the highest bit for cast, this only
323  * works if the highest bit is not
324  * in use (This should not happen)
325  *
326  * We used this for all compiler
327  */
328 double
329 u64ToDouble(u64 value)
330 {
331   double r;
332   u64 tmp = value & u64Const(0x7fffffffffffffff);
333   r = (double)(s64)tmp;
334   if (value & u64Const(0x8000000000000000))
335        r +=  9.2233720368547758080e18; /* 2^63 */
336  return r;
337 }
338
339 /* Fake up flags for now, as we aren't keeping track of castling
340    availability yet. [HGM] Change of logic: the flag now only
341    indicates the type of castlings allowed by the rule of the game.
342    The actual rights themselves are maintained in the array
343    castlingRights, as part of the game history, and are not probed
344    by this function.
345  */
346 int
347 PosFlags(index)
348 {
349   int flags = F_ALL_CASTLE_OK;
350   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351   switch (gameInfo.variant) {
352   case VariantSuicide:
353     flags &= ~F_ALL_CASTLE_OK;
354   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355     flags |= F_IGNORE_CHECK;
356   case VariantLosers:
357     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358     break;
359   case VariantAtomic:
360     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361     break;
362   case VariantKriegspiel:
363     flags |= F_KRIEGSPIEL_CAPTURE;
364     break;
365   case VariantCapaRandom: 
366   case VariantFischeRandom:
367     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368   case VariantNoCastle:
369   case VariantShatranj:
370   case VariantCourier:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /* 
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char  epStatus[MAX_MOVES];
460 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int   initialRulePlies, FENrulePlies;
465 char  FENepStatus;
466 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 int loadFlag = 0; 
468 int shuffleOpenings;
469
470 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_SIZE>=10)
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
570
571 #if (BOARD_SIZE>=12)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<MAX_MOVES; i++ ) {
662             pvInfoList[i].depth = -1;
663             epStatus[i]=EP_NONE;
664             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { int norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815     
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825     
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830 #if 0
831         char *p, *q;
832         q = first.program;
833         while (*q != ' ' && *q != NULLCHAR) q++;
834         p = q;
835         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837         sprintf(programVersion, "%s + ", PACKAGE_STRING);
838         strncat(programVersion, p, q - p);
839 #else
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 #endif
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantBerolina:   /* might work if TestLegality is off */
902       case VariantCapaRandom: /* should work */
903       case VariantJanus:      /* should work */
904       case VariantSuper:      /* experimental */
905       case VariantGreat:      /* experimental, requires legality testing to be off */
906         break;
907       }
908     }
909
910     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
911     InitEngineUCI( installDir, &second );
912 }
913
914 int NextIntegerFromString( char ** str, long * value )
915 {
916     int result = -1;
917     char * s = *str;
918
919     while( *s == ' ' || *s == '\t' ) {
920         s++;
921     }
922
923     *value = 0;
924
925     if( *s >= '0' && *s <= '9' ) {
926         while( *s >= '0' && *s <= '9' ) {
927             *value = *value * 10 + (*s - '0');
928             s++;
929         }
930
931         result = 0;
932     }
933
934     *str = s;
935
936     return result;
937 }
938
939 int NextTimeControlFromString( char ** str, long * value )
940 {
941     long temp;
942     int result = NextIntegerFromString( str, &temp );
943
944     if( result == 0 ) {
945         *value = temp * 60; /* Minutes */
946         if( **str == ':' ) {
947             (*str)++;
948             result = NextIntegerFromString( str, &temp );
949             *value += temp; /* Seconds */
950         }
951     }
952
953     return result;
954 }
955
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
958     int result = -1; long temp, temp2;
959
960     if(**str != '+') return -1; // old params remain in force!
961     (*str)++;
962     if( NextTimeControlFromString( str, &temp ) ) return -1;
963
964     if(**str != '/') {
965         /* time only: incremental or sudden-death time control */
966         if(**str == '+') { /* increment follows; read it */
967             (*str)++;
968             if(result = NextIntegerFromString( str, &temp2)) return -1;
969             *inc = temp2 * 1000;
970         } else *inc = 0;
971         *moves = 0; *tc = temp * 1000; 
972         return 0;
973     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
974
975     (*str)++; /* classical time control */
976     result = NextTimeControlFromString( str, &temp2);
977     if(result == 0) {
978         *moves = temp/60;
979         *tc    = temp2 * 1000;
980         *inc   = 0;
981     }
982     return result;
983 }
984
985 int GetTimeQuota(int movenr)
986 {   /* [HGM] get time to add from the multi-session time-control string */
987     int moves=1; /* kludge to force reading of first session */
988     long time, increment;
989     char *s = fullTimeControlString;
990
991     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992     do {
993         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995         if(movenr == -1) return time;    /* last move before new session     */
996         if(!moves) return increment;     /* current session is incremental   */
997         if(movenr >= 0) movenr -= moves; /* we already finished this session */
998     } while(movenr >= -1);               /* try again for next session       */
999
1000     return 0; // no new time quota on this move
1001 }
1002
1003 int
1004 ParseTimeControl(tc, ti, mps)
1005      char *tc;
1006      int ti;
1007      int mps;
1008 {
1009 #if 0
1010     int matched, min, sec;
1011
1012     matched = sscanf(tc, "%d:%d", &min, &sec);
1013     if (matched == 1) {
1014         timeControl = min * 60 * 1000;
1015     } else if (matched == 2) {
1016         timeControl = (min * 60 + sec) * 1000;
1017     } else {
1018         return FALSE;
1019     }
1020 #else
1021     long tc1;
1022     long tc2;
1023     char buf[MSG_SIZ];
1024
1025     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1026     if(ti > 0) {
1027         if(mps)
1028              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029         else sprintf(buf, "+%s+%d", tc, ti);
1030     } else {
1031         if(mps)
1032              sprintf(buf, "+%d/%s", mps, tc);
1033         else sprintf(buf, "+%s", tc);
1034     }
1035     fullTimeControlString = StrSave(buf);
1036
1037     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038         return FALSE;
1039     }
1040
1041     if( *tc == '/' ) {
1042         /* Parse second time control */
1043         tc++;
1044
1045         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1046             return FALSE;
1047         }
1048
1049         if( tc2 == 0 ) {
1050             return FALSE;
1051         }
1052
1053         timeControl_2 = tc2 * 1000;
1054     }
1055     else {
1056         timeControl_2 = 0;
1057     }
1058
1059     if( tc1 == 0 ) {
1060         return FALSE;
1061     }
1062
1063     timeControl = tc1 * 1000;
1064 #endif
1065
1066     if (ti >= 0) {
1067         timeIncrement = ti * 1000;  /* convert to ms */
1068         movesPerSession = 0;
1069     } else {
1070         timeIncrement = 0;
1071         movesPerSession = mps;
1072     }
1073     return TRUE;
1074 }
1075
1076 void
1077 InitBackEnd2()
1078 {
1079     if (appData.debugMode) {
1080         fprintf(debugFP, "%s\n", programVersion);
1081     }
1082
1083     if (appData.matchGames > 0) {
1084         appData.matchMode = TRUE;
1085     } else if (appData.matchMode) {
1086         appData.matchGames = 1;
1087     }
1088     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089         appData.matchGames = appData.sameColorGames;
1090     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1093     }
1094     Reset(TRUE, FALSE);
1095     if (appData.noChessProgram || first.protocolVersion == 1) {
1096       InitBackEnd3();
1097     } else {
1098       /* kludge: allow timeout for initial "feature" commands */
1099       FreezeUI();
1100       DisplayMessage("", _("Starting chess program"));
1101       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102     }
1103 }
1104
1105 void
1106 InitBackEnd3 P((void))
1107 {
1108     GameMode initialMode;
1109     char buf[MSG_SIZ];
1110     int err;
1111
1112     InitChessProgram(&first, startedFromSetupPosition);
1113
1114
1115     if (appData.icsActive) {
1116 #ifdef WIN32
1117         /* [DM] Make a console window if needed [HGM] merged ifs */
1118         ConsoleCreate(); 
1119 #endif
1120         err = establish();
1121         if (err != 0) {
1122             if (*appData.icsCommPort != NULLCHAR) {
1123                 sprintf(buf, _("Could not open comm port %s"),  
1124                         appData.icsCommPort);
1125             } else {
1126                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1127                         appData.icsHost, appData.icsPort);
1128             }
1129             DisplayFatalError(buf, err, 1);
1130             return;
1131         }
1132         SetICSMode();
1133         telnetISR =
1134           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1135         fromUserISR =
1136           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137     } else if (appData.noChessProgram) {
1138         SetNCPMode();
1139     } else {
1140         SetGNUMode();
1141     }
1142
1143     if (*appData.cmailGameName != NULLCHAR) {
1144         SetCmailMode();
1145         OpenLoopback(&cmailPR);
1146         cmailISR =
1147           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149     
1150     ThawUI();
1151     DisplayMessage("", "");
1152     if (StrCaseCmp(appData.initialMode, "") == 0) {
1153       initialMode = BeginningOfGame;
1154     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155       initialMode = TwoMachinesPlay;
1156     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157       initialMode = AnalyzeFile; 
1158     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159       initialMode = AnalyzeMode;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161       initialMode = MachinePlaysWhite;
1162     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163       initialMode = MachinePlaysBlack;
1164     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165       initialMode = EditGame;
1166     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167       initialMode = EditPosition;
1168     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169       initialMode = Training;
1170     } else {
1171       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172       DisplayFatalError(buf, 0, 2);
1173       return;
1174     }
1175
1176     if (appData.matchMode) {
1177         /* Set up machine vs. machine match */
1178         if (appData.noChessProgram) {
1179             DisplayFatalError(_("Can't have a match with no chess programs"),
1180                               0, 2);
1181             return;
1182         }
1183         matchMode = TRUE;
1184         matchGame = 1;
1185         if (*appData.loadGameFile != NULLCHAR) {
1186             int index = appData.loadGameIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadGameFromFile(appData.loadGameFile,
1189                                   index,
1190                                   appData.loadGameFile, FALSE)) {
1191                 DisplayFatalError(_("Bad game file"), 0, 1);
1192                 return;
1193             }
1194         } else if (*appData.loadPositionFile != NULLCHAR) {
1195             int index = appData.loadPositionIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadPositionFromFile(appData.loadPositionFile,
1198                                       index,
1199                                       appData.loadPositionFile)) {
1200                 DisplayFatalError(_("Bad position file"), 0, 1);
1201                 return;
1202             }
1203         }
1204         TwoMachinesEvent();
1205     } else if (*appData.cmailGameName != NULLCHAR) {
1206         /* Set up cmail mode */
1207         ReloadCmailMsgEvent(TRUE);
1208     } else {
1209         /* Set up other modes */
1210         if (initialMode == AnalyzeFile) {
1211           if (*appData.loadGameFile == NULLCHAR) {
1212             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213             return;
1214           }
1215         }
1216         if (*appData.loadGameFile != NULLCHAR) {
1217             (void) LoadGameFromFile(appData.loadGameFile,
1218                                     appData.loadGameIndex,
1219                                     appData.loadGameFile, TRUE);
1220         } else if (*appData.loadPositionFile != NULLCHAR) {
1221             (void) LoadPositionFromFile(appData.loadPositionFile,
1222                                         appData.loadPositionIndex,
1223                                         appData.loadPositionFile);
1224             /* [HGM] try to make self-starting even after FEN load */
1225             /* to allow automatic setup of fairy variants with wtm */
1226             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227                 gameMode = BeginningOfGame;
1228                 setboardSpoiledMachineBlack = 1;
1229             }
1230             /* [HGM] loadPos: make that every new game uses the setup */
1231             /* from file as long as we do not switch variant          */
1232             if(!blackPlaysFirst) { int i;
1233                 startedFromPositionFile = TRUE;
1234                 CopyBoard(filePosition, boards[0]);
1235                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1236             }
1237         }
1238         if (initialMode == AnalyzeMode) {
1239           if (appData.noChessProgram) {
1240             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245             return;
1246           }
1247           AnalyzeModeEvent();
1248         } else if (initialMode == AnalyzeFile) {
1249           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250           ShowThinkingEvent();
1251           AnalyzeFileEvent();
1252           AnalysisPeriodicEvent(1);
1253         } else if (initialMode == MachinePlaysWhite) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           MachineWhiteEvent();
1265         } else if (initialMode == MachinePlaysBlack) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineBlackEvent();
1277         } else if (initialMode == TwoMachinesPlay) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           TwoMachinesEvent();
1289         } else if (initialMode == EditGame) {
1290           EditGameEvent();
1291         } else if (initialMode == EditPosition) {
1292           EditPositionEvent();
1293         } else if (initialMode == Training) {
1294           if (*appData.loadGameFile == NULLCHAR) {
1295             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1296             return;
1297           }
1298           TrainingEvent();
1299         }
1300     }
1301 }
1302
1303 /*
1304  * Establish will establish a contact to a remote host.port.
1305  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306  *  used to talk to the host.
1307  * Returns 0 if okay, error code if not.
1308  */
1309 int
1310 establish()
1311 {
1312     char buf[MSG_SIZ];
1313
1314     if (*appData.icsCommPort != NULLCHAR) {
1315         /* Talk to the host through a serial comm port */
1316         return OpenCommPort(appData.icsCommPort, &icsPR);
1317
1318     } else if (*appData.gateway != NULLCHAR) {
1319         if (*appData.remoteShell == NULLCHAR) {
1320             /* Use the rcmd protocol to run telnet program on a gateway host */
1321             snprintf(buf, sizeof(buf), "%s %s %s",
1322                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1323             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1324
1325         } else {
1326             /* Use the rsh program to run telnet program on a gateway host */
1327             if (*appData.remoteUser == NULLCHAR) {
1328                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329                         appData.gateway, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             } else {
1332                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333                         appData.remoteShell, appData.gateway, 
1334                         appData.remoteUser, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             }
1337             return StartChildProcess(buf, "", &icsPR);
1338
1339         }
1340     } else if (appData.useTelnet) {
1341         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1342
1343     } else {
1344         /* TCP socket interface differs somewhat between
1345            Unix and NT; handle details in the front end.
1346            */
1347         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1348     }
1349 }
1350
1351 void
1352 show_bytes(fp, buf, count)
1353      FILE *fp;
1354      char *buf;
1355      int count;
1356 {
1357     while (count--) {
1358         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359             fprintf(fp, "\\%03o", *buf & 0xff);
1360         } else {
1361             putc(*buf, fp);
1362         }
1363         buf++;
1364     }
1365     fflush(fp);
1366 }
1367
1368 /* Returns an errno value */
1369 int
1370 OutputMaybeTelnet(pr, message, count, outError)
1371      ProcRef pr;
1372      char *message;
1373      int count;
1374      int *outError;
1375 {
1376     char buf[8192], *p, *q, *buflim;
1377     int left, newcount, outcount;
1378
1379     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380         *appData.gateway != NULLCHAR) {
1381         if (appData.debugMode) {
1382             fprintf(debugFP, ">ICS: ");
1383             show_bytes(debugFP, message, count);
1384             fprintf(debugFP, "\n");
1385         }
1386         return OutputToProcess(pr, message, count, outError);
1387     }
1388
1389     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1390     p = message;
1391     q = buf;
1392     left = count;
1393     newcount = 0;
1394     while (left) {
1395         if (q >= buflim) {
1396             if (appData.debugMode) {
1397                 fprintf(debugFP, ">ICS: ");
1398                 show_bytes(debugFP, buf, newcount);
1399                 fprintf(debugFP, "\n");
1400             }
1401             outcount = OutputToProcess(pr, buf, newcount, outError);
1402             if (outcount < newcount) return -1; /* to be sure */
1403             q = buf;
1404             newcount = 0;
1405         }
1406         if (*p == '\n') {
1407             *q++ = '\r';
1408             newcount++;
1409         } else if (((unsigned char) *p) == TN_IAC) {
1410             *q++ = (char) TN_IAC;
1411             newcount ++;
1412         }
1413         *q++ = *p++;
1414         newcount++;
1415         left--;
1416     }
1417     if (appData.debugMode) {
1418         fprintf(debugFP, ">ICS: ");
1419         show_bytes(debugFP, buf, newcount);
1420         fprintf(debugFP, "\n");
1421     }
1422     outcount = OutputToProcess(pr, buf, newcount, outError);
1423     if (outcount < newcount) return -1; /* to be sure */
1424     return count;
1425 }
1426
1427 void
1428 read_from_player(isr, closure, message, count, error)
1429      InputSourceRef isr;
1430      VOIDSTAR closure;
1431      char *message;
1432      int count;
1433      int error;
1434 {
1435     int outError, outCount;
1436     static int gotEof = 0;
1437
1438     /* Pass data read from player on to ICS */
1439     if (count > 0) {
1440         gotEof = 0;
1441         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442         if (outCount < count) {
1443             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1444         }
1445     } else if (count < 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448     } else if (gotEof++ > 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451     }
1452 }
1453
1454 void
1455 SendToICS(s)
1456      char *s;
1457 {
1458     int count, outCount, outError;
1459
1460     if (icsPR == NULL) return;
1461
1462     count = strlen(s);
1463     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464     if (outCount < count) {
1465         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466     }
1467 }
1468
1469 /* This is used for sending logon scripts to the ICS. Sending
1470    without a delay causes problems when using timestamp on ICC
1471    (at least on my machine). */
1472 void
1473 SendToICSDelayed(s,msdelay)
1474      char *s;
1475      long msdelay;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     if (appData.debugMode) {
1483         fprintf(debugFP, ">ICS: ");
1484         show_bytes(debugFP, s, count);
1485         fprintf(debugFP, "\n");
1486     }
1487     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1488                                       msdelay);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494
1495 /* Remove all highlighting escape sequences in s
1496    Also deletes any suffix starting with '(' 
1497    */
1498 char *
1499 StripHighlightAndTitle(s)
1500      char *s;
1501 {
1502     static char retbuf[MSG_SIZ];
1503     char *p = retbuf;
1504
1505     while (*s != NULLCHAR) {
1506         while (*s == '\033') {
1507             while (*s != NULLCHAR && !isalpha(*s)) s++;
1508             if (*s != NULLCHAR) s++;
1509         }
1510         while (*s != NULLCHAR && *s != '\033') {
1511             if (*s == '(' || *s == '[') {
1512                 *p = NULLCHAR;
1513                 return retbuf;
1514             }
1515             *p++ = *s++;
1516         }
1517     }
1518     *p = NULLCHAR;
1519     return retbuf;
1520 }
1521
1522 /* Remove all highlighting escape sequences in s */
1523 char *
1524 StripHighlight(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             *p++ = *s++;
1537         }
1538     }
1539     *p = NULLCHAR;
1540     return retbuf;
1541 }
1542
1543 char *variantNames[] = VARIANT_NAMES;
1544 char *
1545 VariantName(v)
1546      VariantClass v;
1547 {
1548     return variantNames[v];
1549 }
1550
1551
1552 /* Identify a variant from the strings the chess servers use or the
1553    PGN Variant tag names we use. */
1554 VariantClass
1555 StringToVariant(e)
1556      char *e;
1557 {
1558     char *p;
1559     int wnum = -1;
1560     VariantClass v = VariantNormal;
1561     int i, found = FALSE;
1562     char buf[MSG_SIZ];
1563
1564     if (!e) return v;
1565
1566     /* [HGM] skip over optional board-size prefixes */
1567     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569         while( *e++ != '_');
1570     }
1571
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr") 
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in 
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767     
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948
1949 }
1950
1951
1952 void
1953 VariantSwitch(Board board, VariantClass newVariant)
1954 {
1955    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974
1975   if (appData.debugMode) {
1976     fprintf(debugFP, "Switch board from %s to %s\n",
1977                VariantName(gameInfo.variant), VariantName(newVariant));
1978     setbuf(debugFP, NULL);
1979   }
1980     shuffleOpenings = 0;       /* [HGM] shuffle */
1981     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982     switch(newVariant) {
1983             case VariantShogi:
1984               newWidth = 9;  newHeight = 9;
1985               gameInfo.holdingsSize = 7;
1986             case VariantBughouse:
1987             case VariantCrazyhouse:
1988               newHoldingsWidth = 2; break;
1989             default:
1990               newHoldingsWidth = gameInfo.holdingsSize = 0;
1991     }
1992
1993     if(newWidth  != gameInfo.boardWidth  ||
1994        newHeight != gameInfo.boardHeight ||
1995        newHoldingsWidth != gameInfo.holdingsWidth ) {
1996
1997         /* shift position to new playing area, if needed */
1998         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999            for(i=0; i<BOARD_HEIGHT; i++) 
2000                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2002                                                      board[i][j];
2003            for(i=0; i<newHeight; i++) {
2004                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2006            }
2007         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++)
2009                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                  board[i][j];
2012         }
2013
2014         gameInfo.boardWidth  = newWidth;
2015         gameInfo.boardHeight = newHeight;
2016         gameInfo.holdingsWidth = newHoldingsWidth;
2017         gameInfo.variant = newVariant;
2018         InitDrawingSizes(-2, 0);
2019
2020         /* [HGM] The following should definitely be solved in a better way */
2021 #if 0
2022         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024         saveEP = epStatus[0];
2025 #endif
2026         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2027 #if 0
2028         epStatus[0] = saveEP;
2029         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2031 #endif
2032     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2033
2034     forwardMostMove = oldForwardMostMove;
2035     backwardMostMove = oldBackwardMostMove;
2036     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2037 }
2038
2039 static int loggedOn = FALSE;
2040
2041 /*-- Game start info cache: --*/
2042 int gs_gamenum;
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2049
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2052
2053 void
2054 read_from_ics(isr, closure, data, count, error)
2055      InputSourceRef isr;
2056      VOIDSTAR closure;
2057      char *data;
2058      int count;
2059      int error;
2060 {
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2070     
2071     static int started = STARTED_NONE;
2072     static char parse[20000];
2073     static int parse_pos = 0;
2074     static char buf[BUF_SIZE + 1];
2075     static int firstTime = TRUE, intfSet = FALSE;
2076     static ColorClass prevColor = ColorNormal;
2077     static int savingComment = FALSE;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085
2086     if (appData.debugMode) {
2087       if (!error) {
2088         fprintf(debugFP, "<ICS: ");
2089         show_bytes(debugFP, data, count);
2090         fprintf(debugFP, "\n");
2091       }
2092     }
2093
2094     if (appData.debugMode) { int f = forwardMostMove;
2095         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106         /* Copy in new characters, removing nulls and \r's */
2107         buf_len = leftover_len;
2108         for (i = 0; i < count; i++) {
2109             if (data[i] != NULLCHAR && data[i] != '\r')
2110               buf[buf_len++] = data[i];
2111             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2112                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') 
2113                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114         }
2115
2116         buf[buf_len] = NULLCHAR;
2117         next_out = leftover_len;
2118         leftover_start = 0;
2119         
2120         i = 0;
2121         while (i < buf_len) {
2122             /* Deal with part of the TELNET option negotiation
2123                protocol.  We refuse to do anything beyond the
2124                defaults, except that we allow the WILL ECHO option,
2125                which ICS uses to turn off password echoing when we are
2126                directly connected to it.  We reject this option
2127                if localLineEditing mode is on (always on in xboard)
2128                and we are talking to port 23, which might be a real
2129                telnet server that will try to keep WILL ECHO on permanently.
2130              */
2131             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2132                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2133                 unsigned char option;
2134                 oldi = i;
2135                 switch ((unsigned char) buf[++i]) {
2136                   case TN_WILL:
2137                     if (appData.debugMode)
2138                       fprintf(debugFP, "\n<WILL ");
2139                     switch (option = (unsigned char) buf[++i]) {
2140                       case TN_ECHO:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "ECHO ");
2143                         /* Reply only if this is a change, according
2144                            to the protocol rules. */
2145                         if (remoteEchoOption) break;
2146                         if (appData.localLineEditing &&
2147                             atoi(appData.icsPort) == TN_PORT) {
2148                             TelnetRequest(TN_DONT, TN_ECHO);
2149                         } else {
2150                             EchoOff();
2151                             TelnetRequest(TN_DO, TN_ECHO);
2152                             remoteEchoOption = TRUE;
2153                         }
2154                         break;
2155                       default:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "%d ", option);
2158                         /* Whatever this is, we don't want it. */
2159                         TelnetRequest(TN_DONT, option);
2160                         break;
2161                     }
2162                     break;
2163                   case TN_WONT:
2164                     if (appData.debugMode)
2165                       fprintf(debugFP, "\n<WONT ");
2166                     switch (option = (unsigned char) buf[++i]) {
2167                       case TN_ECHO:
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "ECHO ");
2170                         /* Reply only if this is a change, according
2171                            to the protocol rules. */
2172                         if (!remoteEchoOption) break;
2173                         EchoOn();
2174                         TelnetRequest(TN_DONT, TN_ECHO);
2175                         remoteEchoOption = FALSE;
2176                         break;
2177                       default:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", (unsigned char) option);
2180                         /* Whatever this is, it must already be turned
2181                            off, because we never agree to turn on
2182                            anything non-default, so according to the
2183                            protocol rules, we don't reply. */
2184                         break;
2185                     }
2186                     break;
2187                   case TN_DO:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<DO ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       default:
2192                         /* Whatever this is, we refuse to do it. */
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         TelnetRequest(TN_WONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_DONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<DONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       default:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "%d ", option);
2206                         /* Whatever this is, we are already not doing
2207                            it, because we never agree to do anything
2208                            non-default, so according to the protocol
2209                            rules, we don't reply. */
2210                         break;
2211                     }
2212                     break;
2213                   case TN_IAC:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<IAC ");
2216                     /* Doubled IAC; pass it through */
2217                     i--;
2218                     break;
2219                   default:
2220                     if (appData.debugMode)
2221                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2222                     /* Drop all other telnet commands on the floor */
2223                     break;
2224                 }
2225                 if (oldi > next_out)
2226                   SendToPlayer(&buf[next_out], oldi - next_out);
2227                 if (++i > next_out)
2228                   next_out = i;
2229                 continue;
2230             }
2231                 
2232             /* OK, this at least will *usually* work */
2233             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2234                 loggedOn = TRUE;
2235             }
2236             
2237             if (loggedOn && !intfSet) {
2238                 if (ics_type == ICS_ICC) {
2239                   sprintf(str,
2240                           "/set-quietly interface %s\n/set-quietly style 12\n",
2241                           programVersion);
2242
2243                 } else if (ics_type == ICS_CHESSNET) {
2244                   sprintf(str, "/style 12\n");
2245                 } else {
2246                   strcpy(str, "alias $ @\n$set interface ");
2247                   strcat(str, programVersion);
2248                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2249 #ifdef WIN32
2250                   strcat(str, "$iset nohighlight 1\n");
2251 #endif
2252                   strcat(str, "$iset lock 1\n$style 12\n");
2253                 }
2254                 SendToICS(str);
2255                 intfSet = TRUE;
2256             }
2257
2258             if (started == STARTED_COMMENT) {
2259                 /* Accumulate characters in comment */
2260                 parse[parse_pos++] = buf[i];
2261                 if (buf[i] == '\n') {
2262                     parse[parse_pos] = NULLCHAR;
2263                     if(!suppressKibitz) // [HGM] kibitz
2264                         AppendComment(forwardMostMove, StripHighlight(parse));
2265                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2266                         int nrDigit = 0, nrAlph = 0, i;
2267                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2268                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2269                         parse[parse_pos] = NULLCHAR;
2270                         // try to be smart: if it does not look like search info, it should go to
2271                         // ICS interaction window after all, not to engine-output window.
2272                         for(i=0; i<parse_pos; i++) { // count letters and digits
2273                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2274                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2275                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2276                         }
2277                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2278                             int depth=0; float score;
2279                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2280                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2281                                 pvInfoList[forwardMostMove-1].depth = depth;
2282                                 pvInfoList[forwardMostMove-1].score = 100*score;
2283                             }
2284                             OutputKibitz(suppressKibitz, parse);
2285                         } else {
2286                             char tmp[MSG_SIZ];
2287                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2288                             SendToPlayer(tmp, strlen(tmp));
2289                         }
2290                     }
2291                     started = STARTED_NONE;
2292                 } else {
2293                     /* Don't match patterns against characters in chatter */
2294                     i++;
2295                     continue;
2296                 }
2297             }
2298             if (started == STARTED_CHATTER) {
2299                 if (buf[i] != '\n') {
2300                     /* Don't match patterns against characters in chatter */
2301                     i++;
2302                     continue;
2303                 }
2304                 started = STARTED_NONE;
2305             }
2306
2307             /* Kludge to deal with rcmd protocol */
2308             if (firstTime && looking_at(buf, &i, "\001*")) {
2309                 DisplayFatalError(&buf[1], 0, 1);
2310                 continue;
2311             } else {
2312                 firstTime = FALSE;
2313             }
2314
2315             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2316                 ics_type = ICS_ICC;
2317                 ics_prefix = "/";
2318                 if (appData.debugMode)
2319                   fprintf(debugFP, "ics_type %d\n", ics_type);
2320                 continue;
2321             }
2322             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2323                 ics_type = ICS_FICS;
2324                 ics_prefix = "$";
2325                 if (appData.debugMode)
2326                   fprintf(debugFP, "ics_type %d\n", ics_type);
2327                 continue;
2328             }
2329             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2330                 ics_type = ICS_CHESSNET;
2331                 ics_prefix = "/";
2332                 if (appData.debugMode)
2333                   fprintf(debugFP, "ics_type %d\n", ics_type);
2334                 continue;
2335             }
2336
2337             if (!loggedOn &&
2338                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2339                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2340                  looking_at(buf, &i, "will be \"*\""))) {
2341               strcpy(ics_handle, star_match[0]);
2342               continue;
2343             }
2344
2345             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2346               char buf[MSG_SIZ];
2347               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2348               DisplayIcsInteractionTitle(buf);
2349               have_set_title = TRUE;
2350             }
2351
2352             /* skip finger notes */
2353             if (started == STARTED_NONE &&
2354                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2355                  (buf[i] == '1' && buf[i+1] == '0')) &&
2356                 buf[i+2] == ':' && buf[i+3] == ' ') {
2357               started = STARTED_CHATTER;
2358               i += 3;
2359               continue;
2360             }
2361
2362             /* skip formula vars */
2363             if (started == STARTED_NONE &&
2364                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2365               started = STARTED_CHATTER;
2366               i += 3;
2367               continue;
2368             }
2369
2370             oldi = i;
2371             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2372             if (appData.autoKibitz && started == STARTED_NONE && 
2373                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2374                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2375                 if(looking_at(buf, &i, "* kibitzes: ") &&
2376                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2377                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2378                         suppressKibitz = TRUE;
2379                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2380                                 && (gameMode == IcsPlayingWhite)) ||
2381                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2382                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2383                             started = STARTED_CHATTER; // own kibitz we simply discard
2384                         else {
2385                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2386                             parse_pos = 0; parse[0] = NULLCHAR;
2387                             savingComment = TRUE;
2388                             suppressKibitz = gameMode != IcsObserving ? 2 :
2389                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2390                         } 
2391                         continue;
2392                 } else
2393                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2394                     started = STARTED_CHATTER;
2395                     suppressKibitz = TRUE;
2396                 }
2397             } // [HGM] kibitz: end of patch
2398
2399             if (appData.zippyTalk || appData.zippyPlay) {
2400                 /* [DM] Backup address for color zippy lines */
2401                 backup = i;
2402 #if ZIPPY
2403        #ifdef WIN32
2404                if (loggedOn == TRUE)
2405                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2406                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2407        #else
2408                 if (ZippyControl(buf, &i) ||
2409                     ZippyConverse(buf, &i) ||
2410                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2411                       loggedOn = TRUE;
2412                       if (!appData.colorize) continue;
2413                 }
2414        #endif
2415 #endif
2416             } // [DM] 'else { ' deleted
2417                 if (/* Don't color "message" or "messages" output */
2418                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2419                     looking_at(buf, &i, "*. * at *:*: ") ||
2420                     looking_at(buf, &i, "--* (*:*): ") ||
2421                     /* Regular tells and says */
2422                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2423                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2424                     looking_at(buf, &i, "* says: ") ||
2425                     /* Message notifications (same color as tells) */
2426                     looking_at(buf, &i, "* has left a message ") ||
2427                     looking_at(buf, &i, "* just sent you a message:\n") ||
2428                     /* Whispers and kibitzes */
2429                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2430                     looking_at(buf, &i, "* kibitzes: ") ||
2431                     /* Channel tells */
2432                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2433
2434                   if (tkind == 1 && strchr(star_match[0], ':')) {
2435                       /* Avoid "tells you:" spoofs in channels */
2436                      tkind = 3;
2437                   }
2438                   if (star_match[0][0] == NULLCHAR ||
2439                       strchr(star_match[0], ' ') ||
2440                       (tkind == 3 && strchr(star_match[1], ' '))) {
2441                     /* Reject bogus matches */
2442                     i = oldi;
2443                   } else {
2444                     if (appData.colorize) {
2445                       if (oldi > next_out) {
2446                         SendToPlayer(&buf[next_out], oldi - next_out);
2447                         next_out = oldi;
2448                       }
2449                       switch (tkind) {
2450                       case 1:
2451                         Colorize(ColorTell, FALSE);
2452                         curColor = ColorTell;
2453                         break;
2454                       case 2:
2455                         Colorize(ColorKibitz, FALSE);
2456                         curColor = ColorKibitz;
2457                         break;
2458                       case 3:
2459                         p = strrchr(star_match[1], '(');
2460                         if (p == NULL) {
2461                           p = star_match[1];
2462                         } else {
2463                           p++;
2464                         }
2465                         if (atoi(p) == 1) {
2466                           Colorize(ColorChannel1, FALSE);
2467                           curColor = ColorChannel1;
2468                         } else {
2469                           Colorize(ColorChannel, FALSE);
2470                           curColor = ColorChannel;
2471                         }
2472                         break;
2473                       case 5:
2474                         curColor = ColorNormal;
2475                         break;
2476                       }
2477                     }
2478                     if (started == STARTED_NONE && appData.autoComment &&
2479                         (gameMode == IcsObserving ||
2480                          gameMode == IcsPlayingWhite ||
2481                          gameMode == IcsPlayingBlack)) {
2482                       parse_pos = i - oldi;
2483                       memcpy(parse, &buf[oldi], parse_pos);
2484                       parse[parse_pos] = NULLCHAR;
2485                       started = STARTED_COMMENT;
2486                       savingComment = TRUE;
2487                     } else {
2488                       started = STARTED_CHATTER;
2489                       savingComment = FALSE;
2490                     }
2491                     loggedOn = TRUE;
2492                     continue;
2493                   }
2494                 }
2495
2496                 if (looking_at(buf, &i, "* s-shouts: ") ||
2497                     looking_at(buf, &i, "* c-shouts: ")) {
2498                     if (appData.colorize) {
2499                         if (oldi > next_out) {
2500                             SendToPlayer(&buf[next_out], oldi - next_out);
2501                             next_out = oldi;
2502                         }
2503                         Colorize(ColorSShout, FALSE);
2504                         curColor = ColorSShout;
2505                     }
2506                     loggedOn = TRUE;
2507                     started = STARTED_CHATTER;
2508                     continue;
2509                 }
2510
2511                 if (looking_at(buf, &i, "--->")) {
2512                     loggedOn = TRUE;
2513                     continue;
2514                 }
2515
2516                 if (looking_at(buf, &i, "* shouts: ") ||
2517                     looking_at(buf, &i, "--> ")) {
2518                     if (appData.colorize) {
2519                         if (oldi > next_out) {
2520                             SendToPlayer(&buf[next_out], oldi - next_out);
2521                             next_out = oldi;
2522                         }
2523                         Colorize(ColorShout, FALSE);
2524                         curColor = ColorShout;
2525                     }
2526                     loggedOn = TRUE;
2527                     started = STARTED_CHATTER;
2528                     continue;
2529                 }
2530
2531                 if (looking_at( buf, &i, "Challenge:")) {
2532                     if (appData.colorize) {
2533                         if (oldi > next_out) {
2534                             SendToPlayer(&buf[next_out], oldi - next_out);
2535                             next_out = oldi;
2536                         }
2537                         Colorize(ColorChallenge, FALSE);
2538                         curColor = ColorChallenge;
2539                     }
2540                     loggedOn = TRUE;
2541                     continue;
2542                 }
2543
2544                 if (looking_at(buf, &i, "* offers you") ||
2545                     looking_at(buf, &i, "* offers to be") ||
2546                     looking_at(buf, &i, "* would like to") ||
2547                     looking_at(buf, &i, "* requests to") ||
2548                     looking_at(buf, &i, "Your opponent offers") ||
2549                     looking_at(buf, &i, "Your opponent requests")) {
2550
2551                     if (appData.colorize) {
2552                         if (oldi > next_out) {
2553                             SendToPlayer(&buf[next_out], oldi - next_out);
2554                             next_out = oldi;
2555                         }
2556                         Colorize(ColorRequest, FALSE);
2557                         curColor = ColorRequest;
2558                     }
2559                     continue;
2560                 }
2561
2562                 if (looking_at(buf, &i, "* (*) seeking")) {
2563                     if (appData.colorize) {
2564                         if (oldi > next_out) {
2565                             SendToPlayer(&buf[next_out], oldi - next_out);
2566                             next_out = oldi;
2567                         }
2568                         Colorize(ColorSeek, FALSE);
2569                         curColor = ColorSeek;
2570                     }
2571                     continue;
2572             }
2573
2574             if (looking_at(buf, &i, "\\   ")) {
2575                 if (prevColor != ColorNormal) {
2576                     if (oldi > next_out) {
2577                         SendToPlayer(&buf[next_out], oldi - next_out);
2578                         next_out = oldi;
2579                     }
2580                     Colorize(prevColor, TRUE);
2581                     curColor = prevColor;
2582                 }
2583                 if (savingComment) {
2584                     parse_pos = i - oldi;
2585                     memcpy(parse, &buf[oldi], parse_pos);
2586                     parse[parse_pos] = NULLCHAR;
2587                     started = STARTED_COMMENT;
2588                 } else {
2589                     started = STARTED_CHATTER;
2590                 }
2591                 continue;
2592             }
2593
2594             if (looking_at(buf, &i, "Black Strength :") ||
2595                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2596                 looking_at(buf, &i, "<10>") ||
2597                 looking_at(buf, &i, "#@#")) {
2598                 /* Wrong board style */
2599                 loggedOn = TRUE;
2600                 SendToICS(ics_prefix);
2601                 SendToICS("set style 12\n");
2602                 SendToICS(ics_prefix);
2603                 SendToICS("refresh\n");
2604                 continue;
2605             }
2606             
2607             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2608                 ICSInitScript();
2609                 have_sent_ICS_logon = 1;
2610                 continue;
2611             }
2612               
2613             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2614                 (looking_at(buf, &i, "\n<12> ") ||
2615                  looking_at(buf, &i, "<12> "))) {
2616                 loggedOn = TRUE;
2617                 if (oldi > next_out) {
2618                     SendToPlayer(&buf[next_out], oldi - next_out);
2619                 }
2620                 next_out = i;
2621                 started = STARTED_BOARD;
2622                 parse_pos = 0;
2623                 continue;
2624             }
2625
2626             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2627                 looking_at(buf, &i, "<b1> ")) {
2628                 if (oldi > next_out) {
2629                     SendToPlayer(&buf[next_out], oldi - next_out);
2630                 }
2631                 next_out = i;
2632                 started = STARTED_HOLDINGS;
2633                 parse_pos = 0;
2634                 continue;
2635             }
2636
2637             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2638                 loggedOn = TRUE;
2639                 /* Header for a move list -- first line */
2640
2641                 switch (ics_getting_history) {
2642                   case H_FALSE:
2643                     switch (gameMode) {
2644                       case IcsIdle:
2645                       case BeginningOfGame:
2646                         /* User typed "moves" or "oldmoves" while we
2647                            were idle.  Pretend we asked for these
2648                            moves and soak them up so user can step
2649                            through them and/or save them.
2650                            */
2651                         Reset(FALSE, TRUE);
2652                         gameMode = IcsObserving;
2653                         ModeHighlight();
2654                         ics_gamenum = -1;
2655                         ics_getting_history = H_GOT_UNREQ_HEADER;
2656                         break;
2657                       case EditGame: /*?*/
2658                       case EditPosition: /*?*/
2659                         /* Should above feature work in these modes too? */
2660                         /* For now it doesn't */
2661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2662                         break;
2663                       default:
2664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2665                         break;
2666                     }
2667                     break;
2668                   case H_REQUESTED:
2669                     /* Is this the right one? */
2670                     if (gameInfo.white && gameInfo.black &&
2671                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2672                         strcmp(gameInfo.black, star_match[2]) == 0) {
2673                         /* All is well */
2674                         ics_getting_history = H_GOT_REQ_HEADER;
2675                     }
2676                     break;
2677                   case H_GOT_REQ_HEADER:
2678                   case H_GOT_UNREQ_HEADER:
2679                   case H_GOT_UNWANTED_HEADER:
2680                   case H_GETTING_MOVES:
2681                     /* Should not happen */
2682                     DisplayError(_("Error gathering move list: two headers"), 0);
2683                     ics_getting_history = H_FALSE;
2684                     break;
2685                 }
2686
2687                 /* Save player ratings into gameInfo if needed */
2688                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2689                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2690                     (gameInfo.whiteRating == -1 ||
2691                      gameInfo.blackRating == -1)) {
2692
2693                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2694                     gameInfo.blackRating = string_to_rating(star_match[3]);
2695                     if (appData.debugMode)
2696                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2697                               gameInfo.whiteRating, gameInfo.blackRating);
2698                 }
2699                 continue;
2700             }
2701
2702             if (looking_at(buf, &i,
2703               "* * match, initial time: * minute*, increment: * second")) {
2704                 /* Header for a move list -- second line */
2705                 /* Initial board will follow if this is a wild game */
2706                 if (gameInfo.event != NULL) free(gameInfo.event);
2707                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2708                 gameInfo.event = StrSave(str);
2709                 /* [HGM] we switched variant. Translate boards if needed. */
2710                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2711                 continue;
2712             }
2713
2714             if (looking_at(buf, &i, "Move  ")) {
2715                 /* Beginning of a move list */
2716                 switch (ics_getting_history) {
2717                   case H_FALSE:
2718                     /* Normally should not happen */
2719                     /* Maybe user hit reset while we were parsing */
2720                     break;
2721                   case H_REQUESTED:
2722                     /* Happens if we are ignoring a move list that is not
2723                      * the one we just requested.  Common if the user
2724                      * tries to observe two games without turning off
2725                      * getMoveList */
2726                     break;
2727                   case H_GETTING_MOVES:
2728                     /* Should not happen */
2729                     DisplayError(_("Error gathering move list: nested"), 0);
2730                     ics_getting_history = H_FALSE;
2731                     break;
2732                   case H_GOT_REQ_HEADER:
2733                     ics_getting_history = H_GETTING_MOVES;
2734                     started = STARTED_MOVES;
2735                     parse_pos = 0;
2736                     if (oldi > next_out) {
2737                         SendToPlayer(&buf[next_out], oldi - next_out);
2738                     }
2739                     break;
2740                   case H_GOT_UNREQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES_NOHIDE;
2743                     parse_pos = 0;
2744                     break;
2745                   case H_GOT_UNWANTED_HEADER:
2746                     ics_getting_history = H_FALSE;
2747                     break;
2748                 }
2749                 continue;
2750             }                           
2751             
2752             if (looking_at(buf, &i, "% ") ||
2753                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2754                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2755                 savingComment = FALSE;
2756                 switch (started) {
2757                   case STARTED_MOVES:
2758                   case STARTED_MOVES_NOHIDE:
2759                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2760                     parse[parse_pos + i - oldi] = NULLCHAR;
2761                     ParseGameHistory(parse);
2762 #if ZIPPY
2763                     if (appData.zippyPlay && first.initDone) {
2764                         FeedMovesToProgram(&first, forwardMostMove);
2765                         if (gameMode == IcsPlayingWhite) {
2766                             if (WhiteOnMove(forwardMostMove)) {
2767                                 if (first.sendTime) {
2768                                   if (first.useColors) {
2769                                     SendToProgram("black\n", &first); 
2770                                   }
2771                                   SendTimeRemaining(&first, TRUE);
2772                                 }
2773 #if 0
2774                                 if (first.useColors) {
2775                                   SendToProgram("white\ngo\n", &first);
2776                                 } else {
2777                                   SendToProgram("go\n", &first);
2778                                 }
2779 #else
2780                                 if (first.useColors) {
2781                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2782                                 }
2783                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2784 #endif
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 0
2806                                 if (first.useColors) {
2807                                   SendToProgram("black\ngo\n", &first);
2808                                 } else {
2809                                   SendToProgram("go\n", &first);
2810                                 }
2811 #else
2812                                 if (first.useColors) {
2813                                   SendToProgram("black\n", &first);
2814                                 }
2815                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2816 #endif
2817                                 first.maybeThinking = TRUE;
2818                             } else {
2819                                 if (first.usePlayother) {
2820                                   if (first.sendTime) {
2821                                     SendTimeRemaining(&first, FALSE);
2822                                   }
2823                                   SendToProgram("playother\n", &first);
2824                                   firstMove = FALSE;
2825                                 } else {
2826                                   firstMove = TRUE;
2827                                 }
2828                             }
2829                         }                       
2830                     }
2831 #endif
2832                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2833                         /* Moves came from oldmoves or moves command
2834                            while we weren't doing anything else.
2835                            */
2836                         currentMove = forwardMostMove;
2837                         ClearHighlights();/*!!could figure this out*/
2838                         flipView = appData.flipView;
2839                         DrawPosition(FALSE, boards[currentMove]);
2840                         DisplayBothClocks();
2841                         sprintf(str, "%s vs. %s",
2842                                 gameInfo.white, gameInfo.black);
2843                         DisplayTitle(str);
2844                         gameMode = IcsIdle;
2845                     } else {
2846                         /* Moves were history of an active game */
2847                         if (gameInfo.resultDetails != NULL) {
2848                             free(gameInfo.resultDetails);
2849                             gameInfo.resultDetails = NULL;
2850                         }
2851                     }
2852                     HistorySet(parseList, backwardMostMove,
2853                                forwardMostMove, currentMove-1);
2854                     DisplayMove(currentMove - 1);
2855                     if (started == STARTED_MOVES) next_out = i;
2856                     started = STARTED_NONE;
2857                     ics_getting_history = H_FALSE;
2858                     break;
2859
2860                   case STARTED_OBSERVE:
2861                     started = STARTED_NONE;
2862                     SendToICS(ics_prefix);
2863                     SendToICS("refresh\n");
2864                     break;
2865
2866                   default:
2867                     break;
2868                 }
2869                 if(bookHit) { // [HGM] book: simulate book reply
2870                     static char bookMove[MSG_SIZ]; // a bit generous?
2871
2872                     programStats.nodes = programStats.depth = programStats.time = 
2873                     programStats.score = programStats.got_only_move = 0;
2874                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2875
2876                     strcpy(bookMove, "move ");
2877                     strcat(bookMove, bookHit);
2878                     HandleMachineMove(bookMove, &first);
2879                 }
2880                 continue;
2881             }
2882             
2883             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2884                  started == STARTED_HOLDINGS ||
2885                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2886                 /* Accumulate characters in move list or board */
2887                 parse[parse_pos++] = buf[i];
2888             }
2889             
2890             /* Start of game messages.  Mostly we detect start of game
2891                when the first board image arrives.  On some versions
2892                of the ICS, though, we need to do a "refresh" after starting
2893                to observe in order to get the current board right away. */
2894             if (looking_at(buf, &i, "Adding game * to observation list")) {
2895                 started = STARTED_OBSERVE;
2896                 continue;
2897             }
2898
2899             /* Handle auto-observe */
2900             if (appData.autoObserve &&
2901                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2902                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2903                 char *player;
2904                 /* Choose the player that was highlighted, if any. */
2905                 if (star_match[0][0] == '\033' ||
2906                     star_match[1][0] != '\033') {
2907                     player = star_match[0];
2908                 } else {
2909                     player = star_match[2];
2910                 }
2911                 sprintf(str, "%sobserve %s\n",
2912                         ics_prefix, StripHighlightAndTitle(player));
2913                 SendToICS(str);
2914
2915                 /* Save ratings from notify string */
2916                 strcpy(player1Name, star_match[0]);
2917                 player1Rating = string_to_rating(star_match[1]);
2918                 strcpy(player2Name, star_match[2]);
2919                 player2Rating = string_to_rating(star_match[3]);
2920
2921                 if (appData.debugMode)
2922                   fprintf(debugFP, 
2923                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2924                           player1Name, player1Rating,
2925                           player2Name, player2Rating);
2926
2927                 continue;
2928             }
2929
2930             /* Deal with automatic examine mode after a game,
2931                and with IcsObserving -> IcsExamining transition */
2932             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2933                 looking_at(buf, &i, "has made you an examiner of game *")) {
2934
2935                 int gamenum = atoi(star_match[0]);
2936                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2937                     gamenum == ics_gamenum) {
2938                     /* We were already playing or observing this game;
2939                        no need to refetch history */
2940                     gameMode = IcsExamining;
2941                     if (pausing) {
2942                         pauseExamForwardMostMove = forwardMostMove;
2943                     } else if (currentMove < forwardMostMove) {
2944                         ForwardInner(forwardMostMove);
2945                     }
2946                 } else {
2947                     /* I don't think this case really can happen */
2948                     SendToICS(ics_prefix);
2949                     SendToICS("refresh\n");
2950                 }
2951                 continue;
2952             }    
2953             
2954             /* Error messages */
2955 //          if (ics_user_moved) {
2956             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2957                 if (looking_at(buf, &i, "Illegal move") ||
2958                     looking_at(buf, &i, "Not a legal move") ||
2959                     looking_at(buf, &i, "Your king is in check") ||
2960                     looking_at(buf, &i, "It isn't your turn") ||
2961                     looking_at(buf, &i, "It is not your move")) {
2962                     /* Illegal move */
2963                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2964                         currentMove = --forwardMostMove;
2965                         DisplayMove(currentMove - 1); /* before DMError */
2966                         DrawPosition(FALSE, boards[currentMove]);
2967                         SwitchClocks();
2968                         DisplayBothClocks();
2969                     }
2970                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2971                     ics_user_moved = 0;
2972                     continue;
2973                 }
2974             }
2975
2976             if (looking_at(buf, &i, "still have time") ||
2977                 looking_at(buf, &i, "not out of time") ||
2978                 looking_at(buf, &i, "either player is out of time") ||
2979                 looking_at(buf, &i, "has timeseal; checking")) {
2980                 /* We must have called his flag a little too soon */
2981                 whiteFlag = blackFlag = FALSE;
2982                 continue;
2983             }
2984
2985             if (looking_at(buf, &i, "added * seconds to") ||
2986                 looking_at(buf, &i, "seconds were added to")) {
2987                 /* Update the clocks */
2988                 SendToICS(ics_prefix);
2989                 SendToICS("refresh\n");
2990                 continue;
2991             }
2992
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994                 ics_clock_paused = TRUE;
2995                 StopClocks();
2996                 continue;
2997             }
2998
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000                 ics_clock_paused = FALSE;
3001                 StartClocks();
3002                 continue;
3003             }
3004
3005             /* Grab player ratings from the Creating: message.
3006                Note we have to check for the special case when
3007                the ICS inserts things like [white] or [black]. */
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3010                 /* star_matches:
3011                    0    player 1 name (not necessarily white)
3012                    1    player 1 rating
3013                    2    empty, white, or black (IGNORED)
3014                    3    player 2 name (not necessarily black)
3015                    4    player 2 rating
3016                    
3017                    The names/ratings are sorted out when the game
3018                    actually starts (below).
3019                 */
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021                 player1Rating = string_to_rating(star_match[1]);
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023                 player2Rating = string_to_rating(star_match[4]);
3024
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, 
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",
3028                           player1Name, player1Rating,
3029                           player2Name, player2Rating);
3030
3031                 continue;
3032             }
3033             
3034             /* Improved generic start/end-of-game messages */
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037                 /* If tkind == 0: */
3038                 /* star_match[0] is the game number */
3039                 /*           [1] is the white player's name */
3040                 /*           [2] is the black player's name */
3041                 /* For end-of-game: */
3042                 /*           [3] is the reason for the game end */
3043                 /*           [4] is a PGN end game-token, preceded by " " */
3044                 /* For start-of-game: */
3045                 /*           [3] begins with "Creating" or "Continuing" */
3046                 /*           [4] is " *" or empty (don't care). */
3047                 int gamenum = atoi(star_match[0]);
3048                 char *whitename, *blackname, *why, *endtoken;
3049                 ChessMove endtype = (ChessMove) 0;
3050
3051                 if (tkind == 0) {
3052                   whitename = star_match[1];
3053                   blackname = star_match[2];
3054                   why = star_match[3];
3055                   endtoken = star_match[4];
3056                 } else {
3057                   whitename = star_match[1];
3058                   blackname = star_match[3];
3059                   why = star_match[5];
3060                   endtoken = star_match[6];
3061                 }
3062
3063                 /* Game start messages */
3064                 if (strncmp(why, "Creating ", 9) == 0 ||
3065                     strncmp(why, "Continuing ", 11) == 0) {
3066                     gs_gamenum = gamenum;
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);
3068 #if ZIPPY
3069                     if (appData.zippyPlay) {
3070                         ZippyGameStart(whitename, blackname);
3071                     }
3072 #endif /*ZIPPY*/
3073                     continue;
3074                 }
3075
3076                 /* Game end messages */
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078                     ics_gamenum != gamenum) {
3079                     continue;
3080                 }
3081                 while (endtoken[0] == ' ') endtoken++;
3082                 switch (endtoken[0]) {
3083                   case '*':
3084                   default:
3085                     endtype = GameUnfinished;
3086                     break;
3087                   case '0':
3088                     endtype = BlackWins;
3089                     break;
3090                   case '1':
3091                     if (endtoken[1] == '/')
3092                       endtype = GameIsDrawn;
3093                     else
3094                       endtype = WhiteWins;
3095                     break;
3096                 }
3097                 GameEnds(endtype, why, GE_ICS);
3098 #if ZIPPY
3099                 if (appData.zippyPlay && first.initDone) {
3100                     ZippyGameEnd(endtype, why);
3101                     if (first.pr == NULL) {
3102                       /* Start the next process early so that we'll
3103                          be ready for the next challenge */
3104                       StartChessProgram(&first);
3105                     }
3106                     /* Send "new" early, in case this command takes
3107                        a long time to finish, so that we'll be ready
3108                        for the next challenge. */
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3110                     Reset(TRUE, TRUE);
3111                 }
3112 #endif /*ZIPPY*/
3113                 continue;
3114             }
3115
3116             if (looking_at(buf, &i, "Removing game * from observation") ||
3117                 looking_at(buf, &i, "no longer observing game *") ||
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119                 if (gameMode == IcsObserving &&
3120                     atoi(star_match[0]) == ics_gamenum)
3121                   {
3122                       /* icsEngineAnalyze */
3123                       if (appData.icsEngineAnalyze) {
3124                             ExitAnalyzeMode();
3125                             ModeHighlight();
3126                       }
3127                       StopClocks();
3128                       gameMode = IcsIdle;
3129                       ics_gamenum = -1;
3130                       ics_user_moved = FALSE;
3131                   }
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "no longer examining game *")) {
3136                 if (gameMode == IcsExamining &&
3137                     atoi(star_match[0]) == ics_gamenum)
3138                   {
3139                       gameMode = IcsIdle;
3140                       ics_gamenum = -1;
3141                       ics_user_moved = FALSE;
3142                   }
3143                 continue;
3144             }
3145
3146             /* Advance leftover_start past any newlines we find,
3147                so only partial lines can get reparsed */
3148             if (looking_at(buf, &i, "\n")) {
3149                 prevColor = curColor;
3150                 if (curColor != ColorNormal) {
3151                     if (oldi > next_out) {
3152                         SendToPlayer(&buf[next_out], oldi - next_out);
3153                         next_out = oldi;
3154                     }
3155                     Colorize(ColorNormal, FALSE);
3156                     curColor = ColorNormal;
3157                 }
3158                 if (started == STARTED_BOARD) {
3159                     started = STARTED_NONE;
3160                     parse[parse_pos] = NULLCHAR;
3161                     ParseBoard12(parse);
3162                     ics_user_moved = 0;
3163
3164                     /* Send premove here */
3165                     if (appData.premove) {
3166                       char str[MSG_SIZ];
3167                       if (currentMove == 0 &&
3168                           gameMode == IcsPlayingWhite &&
3169                           appData.premoveWhite) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveWhiteText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (currentMove == 1 &&
3176                                  gameMode == IcsPlayingBlack &&
3177                                  appData.premoveBlack) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveBlackText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (gotPremove) {
3184                         gotPremove = 0;
3185                         ClearPremoveHighlights();
3186                         if (appData.debugMode)
3187                           fprintf(debugFP, "Sending premove:\n");
3188                           UserMoveEvent(premoveFromX, premoveFromY, 
3189                                         premoveToX, premoveToY, 
3190                                         premovePromoChar);
3191                       }
3192                     }
3193
3194                     /* Usually suppress following prompt */
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196                         if (looking_at(buf, &i, "*% ")) {
3197                             savingComment = FALSE;
3198                         }
3199                     }
3200                     next_out = i;
3201                 } else if (started == STARTED_HOLDINGS) {
3202                     int gamenum;
3203                     char new_piece[MSG_SIZ];
3204                     started = STARTED_NONE;
3205                     parse[parse_pos] = NULLCHAR;
3206                     if (appData.debugMode)
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208                                                         parse, currentMove);
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210                         gamenum == ics_gamenum) {
3211                         if (gameInfo.variant == VariantNormal) {
3212                           /* [HGM] We seem to switch variant during a game!
3213                            * Presumably no holdings were displayed, so we have
3214                            * to move the position two files to the right to
3215                            * create room for them!
3216                            */
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218                           /* Get a move list just to see the header, which
3219                              will tell us whether this is really bug or zh */
3220                           if (ics_getting_history == H_FALSE) {
3221                             ics_getting_history = H_REQUESTED;
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3223                             SendToICS(str);
3224                           }
3225                         }
3226                         new_piece[0] = NULLCHAR;
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",
3228                                &gamenum, white_holding, black_holding,
3229                                new_piece);
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3232                         /* [HGM] copy holdings to board holdings area */
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3235 #if ZIPPY
3236                         if (appData.zippyPlay && first.initDone) {
3237                             ZippyHoldings(white_holding, black_holding,
3238                                           new_piece);
3239                         }
3240 #endif /*ZIPPY*/
3241                         if (tinyLayout || smallLayout) {
3242                             char wh[16], bh[16];
3243                             PackHolding(wh, white_holding);
3244                             PackHolding(bh, black_holding);
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246                                     gameInfo.white, gameInfo.black);
3247                         } else {
3248                             sprintf(str, "%s [%s] vs. %s [%s]",
3249                                     gameInfo.white, white_holding,
3250                                     gameInfo.black, black_holding);
3251                         }
3252
3253                         DrawPosition(FALSE, boards[currentMove]);
3254                         DisplayTitle(str);
3255                     }
3256                     /* Suppress following prompt */
3257                     if (looking_at(buf, &i, "*% ")) {
3258                         savingComment = FALSE;
3259                     }
3260                     next_out = i;
3261                 }
3262                 continue;
3263             }
3264
3265             i++;                /* skip unparsed character and loop back */
3266         }
3267         
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269             started != STARTED_HOLDINGS && i > next_out) {
3270             SendToPlayer(&buf[next_out], i - next_out);
3271             next_out = i;
3272         }
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3274         
3275         leftover_len = buf_len - leftover_start;
3276         /* if buffer ends with something we couldn't parse,
3277            reparse it after appending the next read */
3278         
3279     } else if (count == 0) {
3280         RemoveInputSource(isr);
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3282     } else {
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);
3284     }
3285 }
3286
3287
3288 /* Board style 12 looks like this:
3289    
3290    <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
3291    
3292  * The "<12> " is stripped before it gets to this routine.  The two
3293  * trailing 0's (flip state and clock ticking) are later addition, and
3294  * some chess servers may not have them, or may have only the first.
3295  * Additional trailing fields may be added in the future.  
3296  */
3297
3298 #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"
3299
3300 #define RELATION_OBSERVING_PLAYED    0
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE      1
3303 #define RELATION_PLAYING_NOTMYMOVE  -1
3304 #define RELATION_EXAMINING           2
3305 #define RELATION_ISOLATED_BOARD     -3
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3307
3308 void
3309 ParseBoard12(string)
3310      char *string;
3311
3312     GameMode newGameMode;
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316     char to_play, board_chars[200];
3317     char move_str[500], str[500], elapsed_time[500];
3318     char black[32], white[32];
3319     Board board;
3320     int prevMove = currentMove;
3321     int ticking = 2;
3322     ChessMove moveType;
3323     int fromX, fromY, toX, toY;
3324     char promoChar;
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326     char *bookHit = NULL; // [HGM] book
3327
3328     fromX = fromY = toX = toY = -1;
3329     
3330     newGame = FALSE;
3331
3332     if (appData.debugMode)
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);
3334
3335     move_str[0] = NULLCHAR;
3336     elapsed_time[0] = NULLCHAR;
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3338         int  i = 0, j;
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340             if(string[i] == ' ') { ranks++; files = 0; }
3341             else files++;
3342             i++;
3343         }
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];
3345         board_chars[i] = '\0';
3346         string += i + 1;
3347     }
3348     n = sscanf(string, PATTERN, &to_play, &double_push,
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350                &gamenum, white, black, &relation, &basetime, &increment,
3351                &white_stren, &black_stren, &white_time, &black_time,
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,
3353                &ticking);
3354
3355     if (n < 21) {
3356         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357         DisplayError(str, 0);
3358         return;
3359     }
3360
3361     /* Convert the move number to internal form */
3362     moveNum = (moveNum - 1) * 2;
3363     if (to_play == 'B') moveNum++;
3364     if (moveNum >= MAX_MOVES) {
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3366                         0, 1);
3367       return;
3368     }
3369     
3370     switch (relation) {
3371       case RELATION_OBSERVING_PLAYED:
3372       case RELATION_OBSERVING_STATIC:
3373         if (gamenum == -1) {
3374             /* Old ICC buglet */
3375             relation = RELATION_OBSERVING_STATIC;
3376         }
3377         newGameMode = IcsObserving;
3378         break;
3379       case RELATION_PLAYING_MYMOVE:
3380       case RELATION_PLAYING_NOTMYMOVE:
3381         newGameMode =
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383             IcsPlayingWhite : IcsPlayingBlack;
3384         break;
3385       case RELATION_EXAMINING:
3386         newGameMode = IcsExamining;
3387         break;
3388       case RELATION_ISOLATED_BOARD:
3389       default:
3390         /* Just display this board.  If user was doing something else,
3391            we will forget about it until the next board comes. */ 
3392         newGameMode = IcsIdle;
3393         break;
3394       case RELATION_STARTING_POSITION:
3395         newGameMode = gameMode;
3396         break;
3397     }
3398     
3399     /* Modify behavior for initial board display on move listing
3400        of wild games.
3401        */
3402     switch (ics_getting_history) {
3403       case H_FALSE:
3404       case H_REQUESTED:
3405         break;
3406       case H_GOT_REQ_HEADER:
3407       case H_GOT_UNREQ_HEADER:
3408         /* This is the initial position of the current game */
3409         gamenum = ics_gamenum;
3410         moveNum = 0;            /* old ICS bug workaround */
3411         if (to_play == 'B') {
3412           startedFromSetupPosition = TRUE;
3413           blackPlaysFirst = TRUE;
3414           moveNum = 1;
3415           if (forwardMostMove == 0) forwardMostMove = 1;
3416           if (backwardMostMove == 0) backwardMostMove = 1;
3417           if (currentMove == 0) currentMove = 1;
3418         }
3419         newGameMode = gameMode;
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3421         break;
3422       case H_GOT_UNWANTED_HEADER:
3423         /* This is an initial board that we don't want */
3424         return;
3425       case H_GETTING_MOVES:
3426         /* Should not happen */
3427         DisplayError(_("Error gathering move list: extra board"), 0);
3428         ics_getting_history = H_FALSE;
3429         return;
3430     }
3431     
3432     /* Take action if this is the first board of a new game, or of a
3433        different game than is currently being displayed.  */
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435         relation == RELATION_ISOLATED_BOARD) {
3436         
3437         /* Forget the old game and get the history (if any) of the new one */
3438         if (gameMode != BeginningOfGame) {
3439           Reset(FALSE, TRUE);
3440         }
3441         newGame = TRUE;
3442         if (appData.autoRaiseBoard) BoardToTop();
3443         prevMove = -3;
3444         if (gamenum == -1) {
3445             newGameMode = IcsIdle;
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447                    appData.getMoveList) {
3448             /* Need to get game history */
3449             ics_getting_history = H_REQUESTED;
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451             SendToICS(str);
3452         }
3453         
3454         /* Initially flip the board to have black on the bottom if playing
3455            black or if the ICS flip flag is set, but let the user change
3456            it with the Flip View button. */
3457         flipView = appData.autoFlipView ? 
3458           (newGameMode == IcsPlayingBlack) || ics_flip :
3459           appData.flipView;
3460         
3461         /* Done with values from previous mode; copy in new ones */
3462         gameMode = newGameMode;
3463         ModeHighlight();
3464         ics_gamenum = gamenum;
3465         if (gamenum == gs_gamenum) {
3466             int klen = strlen(gs_kind);
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468             sprintf(str, "ICS %s", gs_kind);
3469             gameInfo.event = StrSave(str);
3470         } else {
3471             gameInfo.event = StrSave("ICS game");
3472         }
3473         gameInfo.site = StrSave(appData.icsHost);
3474         gameInfo.date = PGNDate();
3475         gameInfo.round = StrSave("-");
3476         gameInfo.white = StrSave(white);
3477         gameInfo.black = StrSave(black);
3478         timeControl = basetime * 60 * 1000;
3479         timeControl_2 = 0;
3480         timeIncrement = increment * 1000;
3481         movesPerSession = 0;
3482         gameInfo.timeControl = TimeControlTagValue();
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );
3484   if (appData.debugMode) {
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487     setbuf(debugFP, NULL);
3488   }
3489
3490         gameInfo.outOfBook = NULL;
3491         
3492         /* Do we have the ratings? */
3493         if (strcmp(player1Name, white) == 0 &&
3494             strcmp(player2Name, black) == 0) {
3495             if (appData.debugMode)
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497                       player1Rating, player2Rating);
3498             gameInfo.whiteRating = player1Rating;
3499             gameInfo.blackRating = player2Rating;
3500         } else if (strcmp(player2Name, white) == 0 &&
3501                    strcmp(player1Name, black) == 0) {
3502             if (appData.debugMode)
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504                       player2Rating, player1Rating);
3505             gameInfo.whiteRating = player2Rating;
3506             gameInfo.blackRating = player1Rating;
3507         }
3508         player1Name[0] = player2Name[0] = NULLCHAR;
3509
3510         /* Silence shouts if requested */
3511         if (appData.quietPlay &&
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513             SendToICS(ics_prefix);
3514             SendToICS("set shout 0\n");
3515         }
3516     }
3517     
3518     /* Deal with midgame name changes */
3519     if (!newGame) {
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521             if (gameInfo.white) free(gameInfo.white);
3522             gameInfo.white = StrSave(white);
3523         }
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525             if (gameInfo.black) free(gameInfo.black);
3526             gameInfo.black = StrSave(black);
3527         }
3528     }
3529     
3530     /* Throw away game result if anything actually changes in examine mode */
3531     if (gameMode == IcsExamining && !newGame) {
3532         gameInfo.result = GameUnfinished;
3533         if (gameInfo.resultDetails != NULL) {
3534             free(gameInfo.resultDetails);
3535             gameInfo.resultDetails = NULL;
3536         }
3537     }
3538     
3539     /* In pausing && IcsExamining mode, we ignore boards coming
3540        in if they are in a different variation than we are. */
3541     if (pauseExamInvalid) return;
3542     if (pausing && gameMode == IcsExamining) {
3543         if (moveNum <= pauseExamForwardMostMove) {
3544             pauseExamInvalid = TRUE;
3545             forwardMostMove = pauseExamForwardMostMove;
3546             return;
3547         }
3548     }
3549     
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3552   }
3553     /* Parse the board */
3554     for (k = 0; k < ranks; k++) {
3555       for (j = 0; j < files; j++)
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557       if(gameInfo.holdingsWidth > 1) {
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3560       }
3561     }
3562     CopyBoard(boards[moveNum], board);
3563     if (moveNum == 0) {
3564         startedFromSetupPosition =
3565           !CompareBoards(board, initialPosition);
3566         if(startedFromSetupPosition)
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3568     }
3569
3570     /* [HGM] Set castling rights. Take the outermost Rooks,
3571        to make it also work for FRC opening positions. Note that board12
3572        is really defective for later FRC positions, as it has no way to
3573        indicate which Rook can castle if they are on the same side of King.
3574        For the initial position we grant rights to the outermost Rooks,
3575        and remember thos rights, and we then copy them on positions
3576        later in an FRC game. This means WB might not recognize castlings with
3577        Rooks that have moved back to their original position as illegal,
3578        but in ICS mode that is not its job anyway.
3579     */
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3582
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584             if(board[0][i] == WhiteRook) j = i;
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[BOARD_HEIGHT-1][k] == bKing)
3601                 initialRights[5] = castlingRights[moveNum][5] = k;
3602     } else { int r;
3603         r = castlingRights[moveNum][0] = initialRights[0];
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605         r = castlingRights[moveNum][1] = initialRights[1];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607         r = castlingRights[moveNum][3] = initialRights[3];
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609         r = castlingRights[moveNum][4] = initialRights[4];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611         /* wildcastle kludge: always assume King has rights */
3612         r = castlingRights[moveNum][2] = initialRights[2];
3613         r = castlingRights[moveNum][5] = initialRights[5];
3614     }
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3617
3618     
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {
3621         /* This was an initial position from a move list, not
3622            the current position */
3623         return;
3624     }
3625     
3626     /* Update currentMove and known move number limits */
3627     newMove = newGame || moveNum > forwardMostMove;
3628
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631         takeback = forwardMostMove - moveNum;
3632         for (i = 0; i < takeback; i++) {
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634              SendToProgram("undo\n", &first);
3635         }
3636     }
3637
3638     if (newGame) {
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;
3640         if (gameMode == IcsExamining && moveNum == 0) {
3641           /* Workaround for ICS limitation: we are not told the wild
3642              type when starting to examine a game.  But if we ask for
3643              the move list, the move list header will tell us */
3644             ics_getting_history = H_REQUESTED;
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3646             SendToICS(str);
3647         }
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650         forwardMostMove = moveNum;
3651         if (!pausing || currentMove > forwardMostMove)
3652           currentMove = forwardMostMove;
3653     } else {
3654         /* New part of history that is not contiguous with old part */ 
3655         if (pausing && gameMode == IcsExamining) {
3656             pauseExamInvalid = TRUE;
3657             forwardMostMove = pauseExamForwardMostMove;
3658             return;
3659         }
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662             ics_getting_history = H_REQUESTED;
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664             SendToICS(str);
3665         }
3666     }
3667     
3668     /* Update the clocks */
3669     if (strchr(elapsed_time, '.')) {
3670       /* Time is in ms */
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3673     } else {
3674       /* Time is in seconds */
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3677     }
3678       
3679
3680 #if ZIPPY
3681     if (appData.zippyPlay && newGame &&
3682         gameMode != IcsObserving && gameMode != IcsIdle &&
3683         gameMode != IcsExamining)
3684       ZippyFirstBoard(moveNum, basetime, increment);
3685 #endif
3686     
3687     /* Put the move on the move list, first converting
3688        to canonical algebraic form. */
3689     if (moveNum > 0) {
3690   if (appData.debugMode) {
3691     if (appData.debugMode) { int f = forwardMostMove;
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3694     }
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698     setbuf(debugFP, NULL);
3699   }
3700         if (moveNum <= backwardMostMove) {
3701             /* We don't know what the board looked like before
3702                this move.  Punt. */
3703             strcpy(parseList[moveNum - 1], move_str);
3704             strcat(parseList[moveNum - 1], " ");
3705             strcat(parseList[moveNum - 1], elapsed_time);
3706             moveList[moveNum - 1][0] = NULLCHAR;
3707         } else if (strcmp(move_str, "none") == 0) {
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709             /* Again, we don't know what the board looked like;
3710                this is really the start of the game. */
3711             parseList[moveNum - 1][0] = NULLCHAR;
3712             moveList[moveNum - 1][0] = NULLCHAR;
3713             backwardMostMove = moveNum;
3714             startedFromSetupPosition = TRUE;
3715             fromX = fromY = toX = toY = -1;
3716         } else {
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3718           //                 So we parse the long-algebraic move string in stead of the SAN move
3719           int valid; char buf[MSG_SIZ], *prom;
3720
3721           // str looks something like "Q/a1-a2"; kill the slash
3722           if(str[1] == '/') 
3723                 sprintf(buf, "%c%s", str[0], str+2);
3724           else  strcpy(buf, str); // might be castling
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3726                 strcat(buf, prom); // long move lacks promo specification!
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728                 if(appData.debugMode) 
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730                 strcpy(move_str, buf);
3731           }
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)
3734                || ParseOneMove(buf, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);
3736           // end of long SAN patch
3737           if (valid) {
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3740                                      fromY, fromX, toY, toX, promoChar,
3741                                      parseList[moveNum-1]);
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743                              castlingRights[moveNum]) ) {
3744               case MT_NONE:
3745               case MT_STALEMATE:
3746               default:
3747                 break;
3748               case MT_CHECK:
3749                 if(gameInfo.variant != VariantShogi)
3750                     strcat(parseList[moveNum - 1], "+");
3751                 break;
3752               case MT_CHECKMATE:
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754                 strcat(parseList[moveNum - 1], "#");
3755                 break;
3756             }
3757             strcat(parseList[moveNum - 1], " ");
3758             strcat(parseList[moveNum - 1], elapsed_time);
3759             /* currentMoveString is set as a side-effect of ParseOneMove */
3760             strcpy(moveList[moveNum - 1], currentMoveString);
3761             strcat(moveList[moveNum - 1], "\n");
3762           } else {
3763             /* Move from ICS was illegal!?  Punt. */
3764   if (appData.debugMode) {
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3767   }
3768 #if 0
3769             if (appData.testLegality && appData.debugMode) {
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771                 DisplayError(str, 0);
3772             }
3773 #endif
3774             strcpy(parseList[moveNum - 1], move_str);
3775             strcat(parseList[moveNum - 1], " ");
3776             strcat(parseList[moveNum - 1], elapsed_time);
3777             moveList[moveNum - 1][0] = NULLCHAR;
3778             fromX = fromY = toX = toY = -1;
3779           }
3780         }
3781   if (appData.debugMode) {
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783     setbuf(debugFP, NULL);
3784   }
3785
3786 #if ZIPPY
3787         /* Send move to chess program (BEFORE animating it). */
3788         if (appData.zippyPlay && !newGame && newMove && 
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795                             move_str);
3796                     DisplayError(str, 0);
3797                 } else {
3798                     if (first.sendTime) {
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800                     }
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802                     if (firstMove && !bookHit) {
3803                         firstMove = FALSE;
3804                         if (first.useColors) {
3805                           SendToProgram(gameMode == IcsPlayingWhite ?
3806                                         "white\ngo\n" :
3807                                         "black\ngo\n", &first);
3808                         } else {
3809                           SendToProgram("go\n", &first);
3810                         }
3811                         first.maybeThinking = TRUE;
3812                     }
3813                 }
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817                 DisplayError(str, 0);
3818               } else {
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820                 SendMoveToProgram(moveNum - 1, &first);
3821               }
3822             }
3823         }
3824 #endif
3825     }
3826
3827     if (moveNum > 0 && !gotPremove) {
3828         /* If move comes from a remote source, animate it.  If it
3829            isn't remote, it will have already been animated. */
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832         }
3833         if (!pausing && appData.highlightLastMove) {
3834             SetHighlights(fromX, fromY, toX, toY);
3835         }
3836     }
3837     
3838     /* Start the clocks */
3839     whiteFlag = blackFlag = FALSE;
3840     appData.clockMode = !(basetime == 0 && increment == 0);
3841     if (ticking == 0) {
3842       ics_clock_paused = TRUE;
3843       StopClocks();
3844     } else if (ticking == 1) {
3845       ics_clock_paused = FALSE;
3846     }
3847     if (gameMode == IcsIdle ||
3848         relation == RELATION_OBSERVING_STATIC ||
3849         relation == RELATION_EXAMINING ||
3850         ics_clock_paused)
3851       DisplayBothClocks();
3852     else
3853       StartClocks();
3854     
3855     /* Display opponents and material strengths */
3856     if (gameInfo.variant != VariantBughouse &&
3857         gameInfo.variant != VariantCrazyhouse) {
3858         if (tinyLayout || smallLayout) {
3859             if(gameInfo.variant == VariantNormal)
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3862                     basetime, increment);
3863             else
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3866                     basetime, increment, (int) gameInfo.variant);
3867         } else {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, VariantName(gameInfo.variant));
3876         }
3877         DisplayTitle(str);
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3880   }
3881     }
3882
3883    
3884     /* Display the board */
3885     if (!pausing) {
3886       
3887       if (appData.premove)
3888           if (!gotPremove || 
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891               ClearPremoveHighlights();
3892
3893       DrawPosition(FALSE, boards[currentMove]);
3894       DisplayMove(moveNum - 1);
3895       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3898         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3899       }
3900     }
3901
3902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3903 #if ZIPPY
3904     if(bookHit) { // [HGM] book: simulate book reply
3905         static char bookMove[MSG_SIZ]; // a bit generous?
3906
3907         programStats.nodes = programStats.depth = programStats.time = 
3908         programStats.score = programStats.got_only_move = 0;
3909         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3910
3911         strcpy(bookMove, "move ");
3912         strcat(bookMove, bookHit);
3913         HandleMachineMove(bookMove, &first);
3914     }
3915 #endif
3916 }
3917
3918 void
3919 GetMoveListEvent()
3920 {
3921     char buf[MSG_SIZ];
3922     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923         ics_getting_history = H_REQUESTED;
3924         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3925         SendToICS(buf);
3926     }
3927 }
3928
3929 void
3930 AnalysisPeriodicEvent(force)
3931      int force;
3932 {
3933     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934          && !force) || !appData.periodicUpdates)
3935       return;
3936
3937     /* Send . command to Crafty to collect stats */
3938     SendToProgram(".\n", &first);
3939
3940     /* Don't send another until we get a response (this makes
3941        us stop sending to old Crafty's which don't understand
3942        the "." command (sending illegal cmds resets node count & time,
3943        which looks bad)) */
3944     programStats.ok_to_send = 0;
3945 }
3946
3947 void
3948 SendMoveToProgram(moveNum, cps)
3949      int moveNum;
3950      ChessProgramState *cps;
3951 {
3952     char buf[MSG_SIZ];
3953
3954     if (cps->useUsermove) {
3955       SendToProgram("usermove ", cps);
3956     }
3957     if (cps->useSAN) {
3958       char *space;
3959       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3960         int len = space - parseList[moveNum];
3961         memcpy(buf, parseList[moveNum], len);
3962         buf[len++] = '\n';
3963         buf[len] = NULLCHAR;
3964       } else {
3965         sprintf(buf, "%s\n", parseList[moveNum]);
3966       }
3967       SendToProgram(buf, cps);
3968     } else {
3969       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3970         AlphaRank(moveList[moveNum], 4);
3971         SendToProgram(moveList[moveNum], cps);
3972         AlphaRank(moveList[moveNum], 4); // and back
3973       } else
3974       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3975        * the engine. It would be nice to have a better way to identify castle 
3976        * moves here. */
3977       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3978                                                                          && cps->useOOCastle) {
3979         int fromX = moveList[moveNum][0] - AAA; 
3980         int fromY = moveList[moveNum][1] - ONE;
3981         int toX = moveList[moveNum][2] - AAA; 
3982         int toY = moveList[moveNum][3] - ONE;
3983         if((boards[moveNum][fromY][fromX] == WhiteKing 
3984             && boards[moveNum][toY][toX] == WhiteRook)
3985            || (boards[moveNum][fromY][fromX] == BlackKing 
3986                && boards[moveNum][toY][toX] == BlackRook)) {
3987           if(toX > fromX) SendToProgram("O-O\n", cps);
3988           else SendToProgram("O-O-O\n", cps);
3989         }
3990         else SendToProgram(moveList[moveNum], cps);
3991       }
3992       else SendToProgram(moveList[moveNum], cps);
3993       /* End of additions by Tord */
3994     }
3995
3996     /* [HGM] setting up the opening has brought engine in force mode! */
3997     /*       Send 'go' if we are in a mode where machine should play. */
3998     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3999         (gameMode == TwoMachinesPlay   ||
4000 #ifdef ZIPPY
4001          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4002 #endif
4003          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4004         SendToProgram("go\n", cps);
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "(extra)\n");
4007   }
4008     }
4009     setboardSpoiledMachineBlack = 0;
4010 }
4011
4012 void
4013 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4014      ChessMove moveType;
4015      int fromX, fromY, toX, toY;
4016 {
4017     char user_move[MSG_SIZ];
4018
4019     switch (moveType) {
4020       default:
4021         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4022                 (int)moveType, fromX, fromY, toX, toY);
4023         DisplayError(user_move + strlen("say "), 0);
4024         break;
4025       case WhiteKingSideCastle:
4026       case BlackKingSideCastle:
4027       case WhiteQueenSideCastleWild:
4028       case BlackQueenSideCastleWild:
4029       /* PUSH Fabien */
4030       case WhiteHSideCastleFR:
4031       case BlackHSideCastleFR:
4032       /* POP Fabien */
4033         sprintf(user_move, "o-o\n");
4034         break;
4035       case WhiteQueenSideCastle:
4036       case BlackQueenSideCastle:
4037       case WhiteKingSideCastleWild:
4038       case BlackKingSideCastleWild:
4039       /* PUSH Fabien */
4040       case WhiteASideCastleFR:
4041       case BlackASideCastleFR:
4042       /* POP Fabien */
4043         sprintf(user_move, "o-o-o\n");
4044         break;
4045       case WhitePromotionQueen:
4046       case BlackPromotionQueen:
4047       case WhitePromotionRook:
4048       case BlackPromotionRook:
4049       case WhitePromotionBishop:
4050       case BlackPromotionBishop:
4051       case WhitePromotionKnight:
4052       case BlackPromotionKnight:
4053       case WhitePromotionKing:
4054       case BlackPromotionKing:
4055       case WhitePromotionChancellor:
4056       case BlackPromotionChancellor:
4057       case WhitePromotionArchbishop:
4058       case BlackPromotionArchbishop:
4059         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4060             sprintf(user_move, "%c%c%c%c=%c\n",
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4062                 PieceToChar(WhiteFerz));
4063         else if(gameInfo.variant == VariantGreat)
4064             sprintf(user_move, "%c%c%c%c=%c\n",
4065                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4066                 PieceToChar(WhiteMan));
4067         else
4068             sprintf(user_move, "%c%c%c%c=%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070                 PieceToChar(PromoPiece(moveType)));
4071         break;
4072       case WhiteDrop:
4073       case BlackDrop:
4074         sprintf(user_move, "%c@%c%c\n",
4075                 ToUpper(PieceToChar((ChessSquare) fromX)),
4076                 AAA + toX, ONE + toY);
4077         break;
4078       case NormalMove:
4079       case WhiteCapturesEnPassant:
4080       case BlackCapturesEnPassant:
4081       case IllegalMove:  /* could be a variant we don't quite understand */
4082         sprintf(user_move, "%c%c%c%c\n",
4083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4084         break;
4085     }
4086     SendToICS(user_move);
4087 }
4088
4089 void
4090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4091      int rf, ff, rt, ft;
4092      char promoChar;
4093      char move[7];
4094 {
4095     if (rf == DROP_RANK) {
4096         sprintf(move, "%c@%c%c\n",
4097                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4098     } else {
4099         if (promoChar == 'x' || promoChar == NULLCHAR) {
4100             sprintf(move, "%c%c%c%c\n",
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4102         } else {
4103             sprintf(move, "%c%c%c%c%c\n",
4104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4105         }
4106     }
4107 }
4108
4109 void
4110 ProcessICSInitScript(f)
4111      FILE *f;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     while (fgets(buf, MSG_SIZ, f)) {
4116         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4117     }
4118
4119     fclose(f);
4120 }
4121
4122
4123 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4124 void
4125 AlphaRank(char *move, int n)
4126 {
4127 //    char *p = move, c; int x, y;
4128
4129     if (appData.debugMode) {
4130         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4131     }
4132
4133     if(move[1]=='*' && 
4134        move[2]>='0' && move[2]<='9' &&
4135        move[3]>='a' && move[3]<='x'    ) {
4136         move[1] = '@';
4137         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4138         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4139     } else
4140     if(move[0]>='0' && move[0]<='9' &&
4141        move[1]>='a' && move[1]<='x' &&
4142        move[2]>='0' && move[2]<='9' &&
4143        move[3]>='a' && move[3]<='x'    ) {
4144         /* input move, Shogi -> normal */
4145         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4146         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4149     } else
4150     if(move[1]=='@' &&
4151        move[3]>='0' && move[3]<='9' &&
4152        move[2]>='a' && move[2]<='x'    ) {
4153         move[1] = '*';
4154         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4156     } else
4157     if(
4158        move[0]>='a' && move[0]<='x' &&
4159        move[3]>='0' && move[3]<='9' &&
4160        move[2]>='a' && move[2]<='x'    ) {
4161          /* output move, normal -> Shogi */
4162         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4163         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4167     }
4168     if (appData.debugMode) {
4169         fprintf(debugFP, "   out = '%s'\n", move);
4170     }
4171 }
4172
4173 /* Parser for moves from gnuchess, ICS, or user typein box */
4174 Boolean
4175 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4176      char *move;
4177      int moveNum;
4178      ChessMove *moveType;
4179      int *fromX, *fromY, *toX, *toY;
4180      char *promoChar;
4181 {       
4182     if (appData.debugMode) {
4183         fprintf(debugFP, "move to parse: %s\n", move);
4184     }
4185     *moveType = yylexstr(moveNum, move);
4186
4187     switch (*moveType) {
4188       case WhitePromotionChancellor:
4189       case BlackPromotionChancellor:
4190       case WhitePromotionArchbishop:
4191       case BlackPromotionArchbishop:
4192       case WhitePromotionQueen:
4193       case BlackPromotionQueen:
4194       case WhitePromotionRook:
4195       case BlackPromotionRook:
4196       case WhitePromotionBishop:
4197       case BlackPromotionBishop:
4198       case WhitePromotionKnight:
4199       case BlackPromotionKnight:
4200       case WhitePromotionKing:
4201       case BlackPromotionKing:
4202       case NormalMove:
4203       case WhiteCapturesEnPassant:
4204       case BlackCapturesEnPassant:
4205       case WhiteKingSideCastle:
4206       case WhiteQueenSideCastle:
4207       case BlackKingSideCastle:
4208       case BlackQueenSideCastle:
4209       case WhiteKingSideCastleWild:
4210       case WhiteQueenSideCastleWild:
4211       case BlackKingSideCastleWild:
4212       case BlackQueenSideCastleWild:
4213       /* Code added by Tord: */
4214       case WhiteHSideCastleFR:
4215       case WhiteASideCastleFR:
4216       case BlackHSideCastleFR:
4217       case BlackASideCastleFR:
4218       /* End of code added by Tord */
4219       case IllegalMove:         /* bug or odd chess variant */
4220         *fromX = currentMoveString[0] - AAA;
4221         *fromY = currentMoveString[1] - ONE;
4222         *toX = currentMoveString[2] - AAA;
4223         *toY = currentMoveString[3] - ONE;
4224         *promoChar = currentMoveString[4];
4225         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4226             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4227     if (appData.debugMode) {
4228         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4229     }
4230             *fromX = *fromY = *toX = *toY = 0;
4231             return FALSE;
4232         }
4233         if (appData.testLegality) {
4234           return (*moveType != IllegalMove);
4235         } else {
4236           return !(fromX == fromY && toX == toY);
4237         }
4238
4239       case WhiteDrop:
4240       case BlackDrop:
4241         *fromX = *moveType == WhiteDrop ?
4242           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4243           (int) CharToPiece(ToLower(currentMoveString[0]));
4244         *fromY = DROP_RANK;
4245         *toX = currentMoveString[2] - AAA;
4246         *toY = currentMoveString[3] - ONE;
4247         *promoChar = NULLCHAR;
4248         return TRUE;
4249
4250       case AmbiguousMove:
4251       case ImpossibleMove:
4252       case (ChessMove) 0:       /* end of file */
4253       case ElapsedTime:
4254       case Comment:
4255       case PGNTag:
4256       case NAG:
4257       case WhiteWins:
4258       case BlackWins:
4259       case GameIsDrawn:
4260       default:
4261     if (appData.debugMode) {
4262         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4263     }
4264         /* bug? */
4265         *fromX = *fromY = *toX = *toY = 0;
4266         *promoChar = NULLCHAR;
4267         return FALSE;
4268     }
4269 }
4270
4271 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4272 // All positions will have equal probability, but the current method will not provide a unique
4273 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4274 #define DARK 1
4275 #define LITE 2
4276 #define ANY 3
4277
4278 int squaresLeft[4];
4279 int piecesLeft[(int)BlackPawn];
4280 int seed, nrOfShuffles;
4281
4282 void GetPositionNumber()
4283 {       // sets global variable seed
4284         int i;
4285
4286         seed = appData.defaultFrcPosition;
4287         if(seed < 0) { // randomize based on time for negative FRC position numbers
4288                 for(i=0; i<50; i++) seed += random();
4289                 seed = random() ^ random() >> 8 ^ random() << 8;
4290                 if(seed<0) seed = -seed;
4291         }
4292 }
4293
4294 int put(Board board, int pieceType, int rank, int n, int shade)
4295 // put the piece on the (n-1)-th empty squares of the given shade
4296 {
4297         int i;
4298
4299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4300                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4301                         board[rank][i] = (ChessSquare) pieceType;
4302                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4303                         squaresLeft[ANY]--;
4304                         piecesLeft[pieceType]--; 
4305                         return i;
4306                 }
4307         }
4308         return -1;
4309 }
4310
4311
4312 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4313 // calculate where the next piece goes, (any empty square), and put it there
4314 {
4315         int i;
4316
4317         i = seed % squaresLeft[shade];
4318         nrOfShuffles *= squaresLeft[shade];
4319         seed /= squaresLeft[shade];
4320         put(board, pieceType, rank, i, shade);
4321 }
4322
4323 void AddTwoPieces(Board board, int pieceType, int rank)
4324 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4325 {
4326         int i, n=squaresLeft[ANY], j=n-1, k;
4327
4328         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4329         i = seed % k;  // pick one
4330         nrOfShuffles *= k;
4331         seed /= k;
4332         while(i >= j) i -= j--;
4333         j = n - 1 - j; i += j;
4334         put(board, pieceType, rank, j, ANY);
4335         put(board, pieceType, rank, i, ANY);
4336 }
4337
4338 void SetUpShuffle(Board board, int number)
4339 {
4340         int i, p, first=1;
4341
4342         GetPositionNumber(); nrOfShuffles = 1;
4343
4344         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4345         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4346         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4347
4348         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4349
4350         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4351             p = (int) board[0][i];
4352             if(p < (int) BlackPawn) piecesLeft[p] ++;
4353             board[0][i] = EmptySquare;
4354         }
4355
4356         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4357             // shuffles restricted to allow normal castling put KRR first
4358             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4359                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4360             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4361                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4363                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4365                 put(board, WhiteRook, 0, 0, ANY);
4366             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4367         }
4368
4369         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4370             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4371             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4372                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4373                 while(piecesLeft[p] >= 2) {
4374                     AddOnePiece(board, p, 0, LITE);
4375                     AddOnePiece(board, p, 0, DARK);
4376                 }
4377                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4378             }
4379
4380         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4381             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4382             // but we leave King and Rooks for last, to possibly obey FRC restriction
4383             if(p == (int)WhiteRook) continue;
4384             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4385             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4386         }
4387
4388         // now everything is placed, except perhaps King (Unicorn) and Rooks
4389
4390         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4391             // Last King gets castling rights
4392             while(piecesLeft[(int)WhiteUnicorn]) {
4393                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4394                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4395             }
4396
4397             while(piecesLeft[(int)WhiteKing]) {
4398                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4399                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4400             }
4401
4402
4403         } else {
4404             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4405             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4406         }
4407
4408         // Only Rooks can be left; simply place them all
4409         while(piecesLeft[(int)WhiteRook]) {
4410                 i = put(board, WhiteRook, 0, 0, ANY);
4411                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4412                         if(first) {
4413                                 first=0;
4414                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4415                         }
4416                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4417                 }
4418         }
4419         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4420             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4421         }
4422
4423         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4424 }
4425
4426 int SetCharTable( char *table, const char * map )
4427 /* [HGM] moved here from winboard.c because of its general usefulness */
4428 /*       Basically a safe strcpy that uses the last character as King */
4429 {
4430     int result = FALSE; int NrPieces;
4431
4432     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4433                     && NrPieces >= 12 && !(NrPieces&1)) {
4434         int i; /* [HGM] Accept even length from 12 to 34 */
4435
4436         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4437         for( i=0; i<NrPieces/2-1; i++ ) {
4438             table[i] = map[i];
4439             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4440         }
4441         table[(int) WhiteKing]  = map[NrPieces/2-1];
4442         table[(int) BlackKing]  = map[NrPieces-1];
4443
4444         result = TRUE;
4445     }
4446
4447     return result;
4448 }
4449
4450 void Prelude(Board board)
4451 {       // [HGM] superchess: random selection of exo-pieces
4452         int i, j, k; ChessSquare p; 
4453         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4454
4455         GetPositionNumber(); // use FRC position number
4456
4457         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4458             SetCharTable(pieceToChar, appData.pieceToCharTable);
4459             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4460                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4461         }
4462
4463         j = seed%4;                 seed /= 4; 
4464         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3 + (seed%3 >= j); seed /= 3; 
4468         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4469         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4470         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4471         j = seed%3;                 seed /= 3; 
4472         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4473         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4474         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4475         j = seed%2 + (seed%2 >= j); seed /= 2; 
4476         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4477         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4478         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4479         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4480         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4481         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4482         put(board, exoPieces[0],    0, 0, ANY);
4483         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4484 }
4485
4486 void
4487 InitPosition(redraw)
4488      int redraw;
4489 {
4490     ChessSquare (* pieces)[BOARD_SIZE];
4491     int i, j, pawnRow, overrule,
4492     oldx = gameInfo.boardWidth,
4493     oldy = gameInfo.boardHeight,
4494     oldh = gameInfo.holdingsWidth,
4495     oldv = gameInfo.variant;
4496
4497     currentMove = forwardMostMove = backwardMostMove = 0;
4498     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4499
4500     /* [AS] Initialize pv info list [HGM] and game status */
4501     {
4502         for( i=0; i<MAX_MOVES; i++ ) {
4503             pvInfoList[i].depth = 0;
4504             epStatus[i]=EP_NONE;
4505             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4506         }
4507
4508         initialRulePlies = 0; /* 50-move counter start */
4509
4510         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4511         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4512     }
4513
4514     
4515     /* [HGM] logic here is completely changed. In stead of full positions */
4516     /* the initialized data only consist of the two backranks. The switch */
4517     /* selects which one we will use, which is than copied to the Board   */
4518     /* initialPosition, which for the rest is initialized by Pawns and    */
4519     /* empty squares. This initial position is then copied to boards[0],  */
4520     /* possibly after shuffling, so that it remains available.            */
4521
4522     gameInfo.holdingsWidth = 0; /* default board sizes */
4523     gameInfo.boardWidth    = 8;
4524     gameInfo.boardHeight   = 8;
4525     gameInfo.holdingsSize  = 0;
4526     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4527     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4528     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4529
4530     switch (gameInfo.variant) {
4531     case VariantFischeRandom:
4532       shuffleOpenings = TRUE;
4533     default:
4534       pieces = FIDEArray;
4535       break;
4536     case VariantShatranj:
4537       pieces = ShatranjArray;
4538       nrCastlingRights = 0;
4539       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4540       break;
4541     case VariantTwoKings:
4542       pieces = twoKingsArray;
4543       break;
4544     case VariantCapaRandom:
4545       shuffleOpenings = TRUE;
4546     case VariantCapablanca:
4547       pieces = CapablancaArray;
4548       gameInfo.boardWidth = 10;
4549       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4550       break;
4551     case VariantGothic:
4552       pieces = GothicArray;
4553       gameInfo.boardWidth = 10;
4554       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4555       break;
4556     case VariantJanus:
4557       pieces = JanusArray;
4558       gameInfo.boardWidth = 10;
4559       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4560       nrCastlingRights = 6;
4561         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4562         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4563         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4564         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4565         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4566         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4567       break;
4568     case VariantFalcon:
4569       pieces = FalconArray;
4570       gameInfo.boardWidth = 10;
4571       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4572       break;
4573     case VariantXiangqi:
4574       pieces = XiangqiArray;
4575       gameInfo.boardWidth  = 9;
4576       gameInfo.boardHeight = 10;
4577       nrCastlingRights = 0;
4578       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4579       break;
4580     case VariantShogi:
4581       pieces = ShogiArray;
4582       gameInfo.boardWidth  = 9;
4583       gameInfo.boardHeight = 9;
4584       gameInfo.holdingsSize = 7;
4585       nrCastlingRights = 0;
4586       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4587       break;
4588     case VariantCourier:
4589       pieces = CourierArray;
4590       gameInfo.boardWidth  = 12;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4593       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4594       break;
4595     case VariantKnightmate:
4596       pieces = KnightmateArray;
4597       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4598       break;
4599     case VariantFairy:
4600       pieces = fairyArray;
4601       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4602       break;
4603     case VariantGreat:
4604       pieces = GreatArray;
4605       gameInfo.boardWidth = 10;
4606       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4607       gameInfo.holdingsSize = 8;
4608       break;
4609     case VariantSuper:
4610       pieces = FIDEArray;
4611       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4612       gameInfo.holdingsSize = 8;
4613       startedFromSetupPosition = TRUE;
4614       break;
4615     case VariantCrazyhouse:
4616     case VariantBughouse:
4617       pieces = FIDEArray;
4618       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4619       gameInfo.holdingsSize = 5;
4620       break;
4621     case VariantWildCastle:
4622       pieces = FIDEArray;
4623       /* !!?shuffle with kings guaranteed to be on d or e file */
4624       shuffleOpenings = 1;
4625       break;
4626     case VariantNoCastle:
4627       pieces = FIDEArray;
4628       nrCastlingRights = 0;
4629       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4630       /* !!?unconstrained back-rank shuffle */
4631       shuffleOpenings = 1;
4632       break;
4633     }
4634
4635     overrule = 0;
4636     if(appData.NrFiles >= 0) {
4637         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4638         gameInfo.boardWidth = appData.NrFiles;
4639     }
4640     if(appData.NrRanks >= 0) {
4641         gameInfo.boardHeight = appData.NrRanks;
4642     }
4643     if(appData.holdingsSize >= 0) {
4644         i = appData.holdingsSize;
4645         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4646         gameInfo.holdingsSize = i;
4647     }
4648     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4649     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4650         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4651
4652     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4653     if(pawnRow < 1) pawnRow = 1;
4654
4655     /* User pieceToChar list overrules defaults */
4656     if(appData.pieceToCharTable != NULL)
4657         SetCharTable(pieceToChar, appData.pieceToCharTable);
4658
4659     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4660
4661         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4662             s = (ChessSquare) 0; /* account holding counts in guard band */
4663         for( i=0; i<BOARD_HEIGHT; i++ )
4664             initialPosition[i][j] = s;
4665
4666         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4667         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4668         initialPosition[pawnRow][j] = WhitePawn;
4669         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4670         if(gameInfo.variant == VariantXiangqi) {
4671             if(j&1) {
4672                 initialPosition[pawnRow][j] = 
4673                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4674                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4675                    initialPosition[2][j] = WhiteCannon;
4676                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4677                 }
4678             }
4679         }
4680         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4681     }
4682     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4683
4684             j=BOARD_LEFT+1;
4685             initialPosition[1][j] = WhiteBishop;
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4687             j=BOARD_RGHT-2;
4688             initialPosition[1][j] = WhiteRook;
4689             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4690     }
4691
4692     if( nrCastlingRights == -1) {
4693         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4694         /*       This sets default castling rights from none to normal corners   */
4695         /* Variants with other castling rights must set them themselves above    */
4696         nrCastlingRights = 6;
4697        
4698         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4699         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4700         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4701         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4702         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4703         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4704      }
4705
4706      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4707      if(gameInfo.variant == VariantGreat) { // promotion commoners
4708         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4709         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4710         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4711         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4712      }
4713 #if 0
4714     if(gameInfo.variant == VariantFischeRandom) {
4715       if( appData.defaultFrcPosition < 0 ) {
4716         ShuffleFRC( initialPosition );
4717       }
4718       else {
4719         SetupFRC( initialPosition, appData.defaultFrcPosition );
4720       }
4721       startedFromSetupPosition = TRUE;
4722     } else 
4723 #else
4724   if (appData.debugMode) {
4725     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4726   }
4727     if(shuffleOpenings) {
4728         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4729         startedFromSetupPosition = TRUE;
4730     }
4731 #endif
4732     if(startedFromPositionFile) {
4733       /* [HGM] loadPos: use PositionFile for every new game */
4734       CopyBoard(initialPosition, filePosition);
4735       for(i=0; i<nrCastlingRights; i++)
4736           castlingRights[0][i] = initialRights[i] = fileRights[i];
4737       startedFromSetupPosition = TRUE;
4738     }
4739
4740     CopyBoard(boards[0], initialPosition);
4741
4742     if(oldx != gameInfo.boardWidth ||
4743        oldy != gameInfo.boardHeight ||
4744        oldh != gameInfo.holdingsWidth
4745 #ifdef GOTHIC
4746        || oldv == VariantGothic ||        // For licensing popups
4747        gameInfo.variant == VariantGothic
4748 #endif
4749 #ifdef FALCON
4750        || oldv == VariantFalcon ||
4751        gameInfo.variant == VariantFalcon
4752 #endif
4753                                          )
4754             InitDrawingSizes(-2 ,0);
4755
4756     if (redraw)
4757       DrawPosition(TRUE, boards[currentMove]);
4758 }
4759
4760 void
4761 SendBoard(cps, moveNum)
4762      ChessProgramState *cps;
4763      int moveNum;
4764 {
4765     char message[MSG_SIZ];
4766     
4767     if (cps->useSetboard) {
4768       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4769       sprintf(message, "setboard %s\n", fen);
4770       SendToProgram(message, cps);
4771       free(fen);
4772
4773     } else {
4774       ChessSquare *bp;
4775       int i, j;
4776       /* Kludge to set black to move, avoiding the troublesome and now
4777        * deprecated "black" command.
4778        */
4779       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4780
4781       SendToProgram("edit\n", cps);
4782       SendToProgram("#\n", cps);
4783       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4784         bp = &boards[moveNum][i][BOARD_LEFT];
4785         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4786           if ((int) *bp < (int) BlackPawn) {
4787             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4788                     AAA + j, ONE + i);
4789             if(message[0] == '+' || message[0] == '~') {
4790                 sprintf(message, "%c%c%c+\n",
4791                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4792                         AAA + j, ONE + i);
4793             }
4794             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4795                 message[1] = BOARD_RGHT   - 1 - j + '1';
4796                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4797             }
4798             SendToProgram(message, cps);
4799           }
4800         }
4801       }
4802     
4803       SendToProgram("c\n", cps);
4804       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4805         bp = &boards[moveNum][i][BOARD_LEFT];
4806         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4807           if (((int) *bp != (int) EmptySquare)
4808               && ((int) *bp >= (int) BlackPawn)) {
4809             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4810                     AAA + j, ONE + i);
4811             if(message[0] == '+' || message[0] == '~') {
4812                 sprintf(message, "%c%c%c+\n",
4813                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4814                         AAA + j, ONE + i);
4815             }
4816             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4817                 message[1] = BOARD_RGHT   - 1 - j + '1';
4818                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4819             }
4820             SendToProgram(message, cps);
4821           }
4822         }
4823       }
4824     
4825       SendToProgram(".\n", cps);
4826     }
4827     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4828 }
4829
4830 int
4831 IsPromotion(fromX, fromY, toX, toY)
4832      int fromX, fromY, toX, toY;
4833 {
4834     /* [HGM] add Shogi promotions */
4835     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4836     ChessSquare piece;
4837
4838     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4839       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4840    /* [HGM] Note to self: line above also weeds out drops */
4841     piece = boards[currentMove][fromY][fromX];
4842     if(gameInfo.variant == VariantShogi) {
4843         promotionZoneSize = 3;
4844         highestPromotingPiece = (int)WhiteKing;
4845         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4846            and if in normal chess we then allow promotion to King, why not
4847            allow promotion of other piece in Shogi?                         */
4848     }
4849     if((int)piece >= BlackPawn) {
4850         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4851              return FALSE;
4852         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4853     } else {
4854         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4855            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4856     }
4857     return ( (int)piece <= highestPromotingPiece );
4858 }
4859
4860 int
4861 InPalace(row, column)
4862      int row, column;
4863 {   /* [HGM] for Xiangqi */
4864     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4865          column < (BOARD_WIDTH + 4)/2 &&
4866          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4867     return FALSE;
4868 }
4869
4870 int
4871 PieceForSquare (x, y)
4872      int x;
4873      int y;
4874 {
4875   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4876      return -1;
4877   else
4878      return boards[currentMove][y][x];
4879 }
4880
4881 int
4882 OKToStartUserMove(x, y)
4883      int x, y;
4884 {
4885     ChessSquare from_piece;
4886     int white_piece;
4887
4888     if (matchMode) return FALSE;
4889     if (gameMode == EditPosition) return TRUE;
4890
4891     if (x >= 0 && y >= 0)
4892       from_piece = boards[currentMove][y][x];
4893     else
4894       from_piece = EmptySquare;
4895
4896     if (from_piece == EmptySquare) return FALSE;
4897
4898     white_piece = (int)from_piece >= (int)WhitePawn &&
4899       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4900
4901     switch (gameMode) {
4902       case PlayFromGameFile:
4903       case AnalyzeFile:
4904       case TwoMachinesPlay:
4905       case EndOfGame:
4906         return FALSE;
4907
4908       case IcsObserving:
4909       case IcsIdle:
4910         return FALSE;
4911
4912       case MachinePlaysWhite:
4913       case IcsPlayingBlack:
4914         if (appData.zippyPlay) return FALSE;
4915         if (white_piece) {
4916             DisplayMoveError(_("You are playing Black"));
4917             return FALSE;
4918         }
4919         break;
4920
4921       case MachinePlaysBlack:
4922       case IcsPlayingWhite:
4923         if (appData.zippyPlay) return FALSE;
4924         if (!white_piece) {
4925             DisplayMoveError(_("You are playing White"));
4926             return FALSE;
4927         }
4928         break;
4929
4930       case EditGame:
4931         if (!white_piece && WhiteOnMove(currentMove)) {
4932             DisplayMoveError(_("It is White's turn"));
4933             return FALSE;
4934         }           
4935         if (white_piece && !WhiteOnMove(currentMove)) {
4936             DisplayMoveError(_("It is Black's turn"));
4937             return FALSE;
4938         }           
4939         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4940             /* Editing correspondence game history */
4941             /* Could disallow this or prompt for confirmation */
4942             cmailOldMove = -1;
4943         }
4944         if (currentMove < forwardMostMove) {
4945             /* Discarding moves */
4946             /* Could prompt for confirmation here,
4947                but I don't think that's such a good idea */
4948             forwardMostMove = currentMove;
4949         }
4950         break;
4951
4952       case BeginningOfGame:
4953         if (appData.icsActive) return FALSE;
4954         if (!appData.noChessProgram) {
4955             if (!white_piece) {
4956                 DisplayMoveError(_("You are playing White"));
4957                 return FALSE;
4958             }
4959         }
4960         break;
4961         
4962       case Training:
4963         if (!white_piece && WhiteOnMove(currentMove)) {
4964             DisplayMoveError(_("It is White's turn"));
4965             return FALSE;
4966         }           
4967         if (white_piece && !WhiteOnMove(currentMove)) {
4968             DisplayMoveError(_("It is Black's turn"));
4969             return FALSE;
4970         }           
4971         break;
4972
4973       default:
4974       case IcsExamining:
4975         break;
4976     }
4977     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4978         && gameMode != AnalyzeFile && gameMode != Training) {
4979         DisplayMoveError(_("Displayed position is not current"));
4980         return FALSE;
4981     }
4982     return TRUE;
4983 }
4984
4985 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4986 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4987 int lastLoadGameUseList = FALSE;
4988 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4989 ChessMove lastLoadGameStart = (ChessMove) 0;
4990
4991
4992 ChessMove
4993 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4994      int fromX, fromY, toX, toY;
4995      int promoChar;
4996 {
4997     ChessMove moveType;
4998     ChessSquare pdown, pup;
4999
5000     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5001     if ((fromX == toX) && (fromY == toY)) {
5002         return ImpossibleMove;
5003     }
5004
5005     /* [HGM] suppress all moves into holdings area and guard band */
5006     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5007             return ImpossibleMove;
5008
5009     /* [HGM] <sameColor> moved to here from winboard.c */
5010     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5011     pdown = boards[currentMove][fromY][fromX];
5012     pup = boards[currentMove][toY][toX];
5013     if (    gameMode != EditPosition &&
5014             (WhitePawn <= pdown && pdown < BlackPawn &&
5015              WhitePawn <= pup && pup < BlackPawn  ||
5016              BlackPawn <= pdown && pdown < EmptySquare &&
5017              BlackPawn <= pup && pup < EmptySquare 
5018             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5019                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5020                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5021         )           )
5022          return ImpossibleMove;
5023
5024     /* Check if the user is playing in turn.  This is complicated because we
5025        let the user "pick up" a piece before it is his turn.  So the piece he
5026        tried to pick up may have been captured by the time he puts it down!
5027        Therefore we use the color the user is supposed to be playing in this
5028        test, not the color of the piece that is currently on the starting
5029        square---except in EditGame mode, where the user is playing both
5030        sides; fortunately there the capture race can't happen.  (It can
5031        now happen in IcsExamining mode, but that's just too bad.  The user
5032        will get a somewhat confusing message in that case.)
5033        */
5034
5035     switch (gameMode) {
5036       case PlayFromGameFile:
5037       case AnalyzeFile:
5038       case TwoMachinesPlay:
5039       case EndOfGame:
5040       case IcsObserving:
5041       case IcsIdle:
5042         /* We switched into a game mode where moves are not accepted,
5043            perhaps while the mouse button was down. */
5044         return ImpossibleMove;
5045
5046       case MachinePlaysWhite:
5047         /* User is moving for Black */
5048         if (WhiteOnMove(currentMove)) {
5049             DisplayMoveError(_("It is White's turn"));
5050             return ImpossibleMove;
5051         }
5052         break;
5053
5054       case MachinePlaysBlack:
5055         /* User is moving for White */
5056         if (!WhiteOnMove(currentMove)) {
5057             DisplayMoveError(_("It is Black's turn"));
5058             return ImpossibleMove;
5059         }
5060         break;
5061
5062       case EditGame:
5063       case IcsExamining:
5064       case BeginningOfGame:
5065       case AnalyzeMode:
5066       case Training:
5067         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5068             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5069             /* User is moving for Black */
5070             if (WhiteOnMove(currentMove)) {
5071                 DisplayMoveError(_("It is White's turn"));
5072                 return ImpossibleMove;
5073             }
5074         } else {
5075             /* User is moving for White */
5076             if (!WhiteOnMove(currentMove)) {
5077                 DisplayMoveError(_("It is Black's turn"));
5078                 return ImpossibleMove;
5079             }
5080         }
5081         break;
5082
5083       case IcsPlayingBlack:
5084         /* User is moving for Black */
5085         if (WhiteOnMove(currentMove)) {
5086             if (!appData.premove) {
5087                 DisplayMoveError(_("It is White's turn"));
5088             } else if (toX >= 0 && toY >= 0) {
5089                 premoveToX = toX;
5090                 premoveToY = toY;
5091                 premoveFromX = fromX;
5092                 premoveFromY = fromY;
5093                 premovePromoChar = promoChar;
5094                 gotPremove = 1;
5095                 if (appData.debugMode) 
5096                     fprintf(debugFP, "Got premove: fromX %d,"
5097                             "fromY %d, toX %d, toY %d\n",
5098                             fromX, fromY, toX, toY);
5099             }
5100             return ImpossibleMove;
5101         }
5102         break;
5103
5104       case IcsPlayingWhite:
5105         /* User is moving for White */
5106         if (!WhiteOnMove(currentMove)) {
5107             if (!appData.premove) {
5108                 DisplayMoveError(_("It is Black's turn"));
5109             } else if (toX >= 0 && toY >= 0) {
5110                 premoveToX = toX;
5111                 premoveToY = toY;
5112                 premoveFromX = fromX;
5113                 premoveFromY = fromY;
5114                 premovePromoChar = promoChar;
5115                 gotPremove = 1;
5116                 if (appData.debugMode) 
5117                     fprintf(debugFP, "Got premove: fromX %d,"
5118                             "fromY %d, toX %d, toY %d\n",
5119                             fromX, fromY, toX, toY);
5120             }
5121             return ImpossibleMove;
5122         }
5123         break;
5124
5125       default:
5126         break;
5127
5128       case EditPosition:
5129         /* EditPosition, empty square, or different color piece;
5130            click-click move is possible */
5131         if (toX == -2 || toY == -2) {
5132             boards[0][fromY][fromX] = EmptySquare;
5133             return AmbiguousMove;
5134         } else if (toX >= 0 && toY >= 0) {
5135             boards[0][toY][toX] = boards[0][fromY][fromX];
5136             boards[0][fromY][fromX] = EmptySquare;
5137             return AmbiguousMove;
5138         }
5139         return ImpossibleMove;
5140     }
5141
5142     /* [HGM] If move started in holdings, it means a drop */
5143     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5144          if( pup != EmptySquare ) return ImpossibleMove;
5145          if(appData.testLegality) {
5146              /* it would be more logical if LegalityTest() also figured out
5147               * which drops are legal. For now we forbid pawns on back rank.
5148               * Shogi is on its own here...
5149               */
5150              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5151                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5152                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5153          }
5154          return WhiteDrop; /* Not needed to specify white or black yet */
5155     }
5156
5157     userOfferedDraw = FALSE;
5158         
5159     /* [HGM] always test for legality, to get promotion info */
5160     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5161                           epStatus[currentMove], castlingRights[currentMove],
5162                                          fromY, fromX, toY, toX, promoChar);
5163
5164     /* [HGM] but possibly ignore an IllegalMove result */
5165     if (appData.testLegality) {
5166         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5167             DisplayMoveError(_("Illegal move"));
5168             return ImpossibleMove;
5169         }
5170     }
5171 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5172     return moveType;
5173     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5174        function is made into one that returns an OK move type if FinishMove
5175        should be called. This to give the calling driver routine the
5176        opportunity to finish the userMove input with a promotion popup,
5177        without bothering the user with this for invalid or illegal moves */
5178
5179 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5180 }
5181
5182 /* Common tail of UserMoveEvent and DropMenuEvent */
5183 int
5184 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5185      ChessMove moveType;
5186      int fromX, fromY, toX, toY;
5187      /*char*/int promoChar;
5188 {
5189     char *bookHit = 0;
5190 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5191     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5192         // [HGM] superchess: suppress promotions to non-available piece
5193         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5194         if(WhiteOnMove(currentMove)) {
5195             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5196         } else {
5197             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5198         }
5199     }
5200
5201     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5202        move type in caller when we know the move is a legal promotion */
5203     if(moveType == NormalMove && promoChar)
5204         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5205 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5206     /* [HGM] convert drag-and-drop piece drops to standard form */
5207     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5208          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5209            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5210                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5211 //         fromX = boards[currentMove][fromY][fromX];
5212            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5213            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5214            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5215            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5216          fromY = DROP_RANK;
5217     }
5218
5219     /* [HGM] <popupFix> The following if has been moved here from
5220        UserMoveEvent(). Because it seemed to belon here (why not allow
5221        piece drops in training games?), and because it can only be
5222        performed after it is known to what we promote. */
5223     if (gameMode == Training) {
5224       /* compare the move played on the board to the next move in the
5225        * game. If they match, display the move and the opponent's response. 
5226        * If they don't match, display an error message.
5227        */
5228       int saveAnimate;
5229       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5230       CopyBoard(testBoard, boards[currentMove]);
5231       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5232
5233       if (CompareBoards(testBoard, boards[currentMove+1])) {
5234         ForwardInner(currentMove+1);
5235
5236         /* Autoplay the opponent's response.
5237          * if appData.animate was TRUE when Training mode was entered,
5238          * the response will be animated.
5239          */
5240         saveAnimate = appData.animate;
5241         appData.animate = animateTraining;
5242         ForwardInner(currentMove+1);
5243         appData.animate = saveAnimate;
5244
5245         /* check for the end of the game */
5246         if (currentMove >= forwardMostMove) {
5247           gameMode = PlayFromGameFile;
5248           ModeHighlight();
5249           SetTrainingModeOff();
5250           DisplayInformation(_("End of game"));
5251         }
5252       } else {
5253         DisplayError(_("Incorrect move"), 0);
5254       }
5255       return 1;
5256     }
5257
5258   /* Ok, now we know that the move is good, so we can kill
5259      the previous line in Analysis Mode */
5260   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5261     forwardMostMove = currentMove;
5262   }
5263
5264   /* If we need the chess program but it's dead, restart it */
5265   ResurrectChessProgram();
5266
5267   /* A user move restarts a paused game*/
5268   if (pausing)
5269     PauseEvent();
5270
5271   thinkOutput[0] = NULLCHAR;
5272
5273   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5274
5275   if (gameMode == BeginningOfGame) {
5276     if (appData.noChessProgram) {
5277       gameMode = EditGame;
5278       SetGameInfo();
5279     } else {
5280       char buf[MSG_SIZ];
5281       gameMode = MachinePlaysBlack;
5282       StartClocks();
5283       SetGameInfo();
5284       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5285       DisplayTitle(buf);
5286       if (first.sendName) {
5287         sprintf(buf, "name %s\n", gameInfo.white);
5288         SendToProgram(buf, &first);
5289       }
5290       StartClocks();
5291     }
5292     ModeHighlight();
5293   }
5294 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5295   /* Relay move to ICS or chess engine */
5296   if (appData.icsActive) {
5297     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5298         gameMode == IcsExamining) {
5299       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5300       ics_user_moved = 1;
5301     }
5302   } else {
5303     if (first.sendTime && (gameMode == BeginningOfGame ||
5304                            gameMode == MachinePlaysWhite ||
5305                            gameMode == MachinePlaysBlack)) {
5306       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5307     }
5308     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5309          // [HGM] book: if program might be playing, let it use book
5310         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5311         first.maybeThinking = TRUE;
5312     } else SendMoveToProgram(forwardMostMove-1, &first);
5313     if (currentMove == cmailOldMove + 1) {
5314       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5315     }
5316   }
5317
5318   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5319
5320   switch (gameMode) {
5321   case EditGame:
5322     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5323                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5324     case MT_NONE:
5325     case MT_CHECK:
5326       break;
5327     case MT_CHECKMATE:
5328     case MT_STAINMATE:
5329       if (WhiteOnMove(currentMove)) {
5330         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5331       } else {
5332         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5333       }
5334       break;
5335     case MT_STALEMATE:
5336       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5337       break;
5338     }
5339     break;
5340     
5341   case MachinePlaysBlack:
5342   case MachinePlaysWhite:
5343     /* disable certain menu options while machine is thinking */
5344     SetMachineThinkingEnables();
5345     break;
5346
5347   default:
5348     break;
5349   }
5350
5351   if(bookHit) { // [HGM] book: simulate book reply
5352         static char bookMove[MSG_SIZ]; // a bit generous?
5353
5354         programStats.nodes = programStats.depth = programStats.time = 
5355         programStats.score = programStats.got_only_move = 0;
5356         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5357
5358         strcpy(bookMove, "move ");
5359         strcat(bookMove, bookHit);
5360         HandleMachineMove(bookMove, &first);
5361   }
5362   return 1;
5363 }
5364
5365 void
5366 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5367      int fromX, fromY, toX, toY;
5368      int promoChar;
5369 {
5370     /* [HGM] This routine was added to allow calling of its two logical
5371        parts from other modules in the old way. Before, UserMoveEvent()
5372        automatically called FinishMove() if the move was OK, and returned
5373        otherwise. I separated the two, in order to make it possible to
5374        slip a promotion popup in between. But that it always needs two
5375        calls, to the first part, (now called UserMoveTest() ), and to
5376        FinishMove if the first part succeeded. Calls that do not need
5377        to do anything in between, can call this routine the old way. 
5378     */
5379     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5380 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5381     if(moveType != ImpossibleMove)
5382         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5383 }
5384
5385 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5386 {
5387 //    char * hint = lastHint;
5388     FrontEndProgramStats stats;
5389
5390     stats.which = cps == &first ? 0 : 1;
5391     stats.depth = cpstats->depth;
5392     stats.nodes = cpstats->nodes;
5393     stats.score = cpstats->score;
5394     stats.time = cpstats->time;
5395     stats.pv = cpstats->movelist;
5396     stats.hint = lastHint;
5397     stats.an_move_index = 0;
5398     stats.an_move_count = 0;
5399
5400     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5401         stats.hint = cpstats->move_name;
5402         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5403         stats.an_move_count = cpstats->nr_moves;
5404     }
5405
5406     SetProgramStats( &stats );
5407 }
5408
5409 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5410 {   // [HGM] book: this routine intercepts moves to simulate book replies
5411     char *bookHit = NULL;
5412
5413     //first determine if the incoming move brings opponent into his book
5414     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5415         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5416     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5417     if(bookHit != NULL && !cps->bookSuspend) {
5418         // make sure opponent is not going to reply after receiving move to book position
5419         SendToProgram("force\n", cps);
5420         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5421     }
5422     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5423     // now arrange restart after book miss
5424     if(bookHit) {
5425         // after a book hit we never send 'go', and the code after the call to this routine
5426         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5427         char buf[MSG_SIZ];
5428         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5429         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5430         SendToProgram(buf, cps);
5431         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5432     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5433         SendToProgram("go\n", cps);
5434         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5435     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5436         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5437             SendToProgram("go\n", cps); 
5438         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5439     }
5440     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5441 }
5442
5443 char *savedMessage;
5444 ChessProgramState *savedState;
5445 void DeferredBookMove(void)
5446 {
5447         if(savedState->lastPing != savedState->lastPong)
5448                     ScheduleDelayedEvent(DeferredBookMove, 10);
5449         else
5450         HandleMachineMove(savedMessage, savedState);
5451 }
5452
5453 void
5454 HandleMachineMove(message, cps)
5455      char *message;
5456      ChessProgramState *cps;
5457 {
5458     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5459     char realname[MSG_SIZ];
5460     int fromX, fromY, toX, toY;
5461     ChessMove moveType;
5462     char promoChar;
5463     char *p;
5464     int machineWhite;
5465     char *bookHit;
5466
5467 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5468     /*
5469      * Kludge to ignore BEL characters
5470      */
5471     while (*message == '\007') message++;
5472
5473     /*
5474      * [HGM] engine debug message: ignore lines starting with '#' character
5475      */
5476     if(cps->debug && *message == '#') return;
5477
5478     /*
5479      * Look for book output
5480      */
5481     if (cps == &first && bookRequested) {
5482         if (message[0] == '\t' || message[0] == ' ') {
5483             /* Part of the book output is here; append it */
5484             strcat(bookOutput, message);
5485             strcat(bookOutput, "  \n");
5486             return;
5487         } else if (bookOutput[0] != NULLCHAR) {
5488             /* All of book output has arrived; display it */
5489             char *p = bookOutput;
5490             while (*p != NULLCHAR) {
5491                 if (*p == '\t') *p = ' ';
5492                 p++;
5493             }
5494             DisplayInformation(bookOutput);
5495             bookRequested = FALSE;
5496             /* Fall through to parse the current output */
5497         }
5498     }
5499
5500     /*
5501      * Look for machine move.
5502      */
5503     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5504         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5505     {
5506         /* This method is only useful on engines that support ping */
5507         if (cps->lastPing != cps->lastPong) {
5508           if (gameMode == BeginningOfGame) {
5509             /* Extra move from before last new; ignore */
5510             if (appData.debugMode) {
5511                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5512             }
5513           } else {
5514             if (appData.debugMode) {
5515                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5516                         cps->which, gameMode);
5517             }
5518
5519             SendToProgram("undo\n", cps);
5520           }
5521           return;
5522         }
5523
5524         switch (gameMode) {
5525           case BeginningOfGame:
5526             /* Extra move from before last reset; ignore */
5527             if (appData.debugMode) {
5528                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5529             }
5530             return;
5531
5532           case EndOfGame:
5533           case IcsIdle:
5534           default:
5535             /* Extra move after we tried to stop.  The mode test is
5536                not a reliable way of detecting this problem, but it's
5537                the best we can do on engines that don't support ping.
5538             */
5539             if (appData.debugMode) {
5540                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5541                         cps->which, gameMode);
5542             }
5543             SendToProgram("undo\n", cps);
5544             return;
5545
5546           case MachinePlaysWhite:
5547           case IcsPlayingWhite:
5548             machineWhite = TRUE;
5549             break;
5550
5551           case MachinePlaysBlack:
5552           case IcsPlayingBlack:
5553             machineWhite = FALSE;
5554             break;
5555
5556           case TwoMachinesPlay:
5557             machineWhite = (cps->twoMachinesColor[0] == 'w');
5558             break;
5559         }
5560         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5561             if (appData.debugMode) {
5562                 fprintf(debugFP,
5563                         "Ignoring move out of turn by %s, gameMode %d"
5564                         ", forwardMost %d\n",
5565                         cps->which, gameMode, forwardMostMove);
5566             }
5567             return;
5568         }
5569
5570     if (appData.debugMode) { int f = forwardMostMove;
5571         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5572                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5573     }
5574         if(cps->alphaRank) AlphaRank(machineMove, 4);
5575         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5576                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5577             /* Machine move could not be parsed; ignore it. */
5578             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5579                     machineMove, cps->which);
5580             DisplayError(buf1, 0);
5581             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5582                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5583             if (gameMode == TwoMachinesPlay) {
5584               GameEnds(machineWhite ? BlackWins : WhiteWins,
5585                        buf1, GE_XBOARD);
5586             }
5587             return;
5588         }
5589
5590         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5591         /* So we have to redo legality test with true e.p. status here,  */
5592         /* to make sure an illegal e.p. capture does not slip through,   */
5593         /* to cause a forfeit on a justified illegal-move complaint      */
5594         /* of the opponent.                                              */
5595         if( gameMode==TwoMachinesPlay && appData.testLegality
5596             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5597                                                               ) {
5598            ChessMove moveType;
5599            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5600                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5601                              fromY, fromX, toY, toX, promoChar);
5602             if (appData.debugMode) {
5603                 int i;
5604                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5605                     castlingRights[forwardMostMove][i], castlingRank[i]);
5606                 fprintf(debugFP, "castling rights\n");
5607             }
5608             if(moveType == IllegalMove) {
5609                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5610                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5611                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5612                            buf1, GE_XBOARD);
5613                 return;
5614            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5615            /* [HGM] Kludge to handle engines that send FRC-style castling
5616               when they shouldn't (like TSCP-Gothic) */
5617            switch(moveType) {
5618              case WhiteASideCastleFR:
5619              case BlackASideCastleFR:
5620                toX+=2;
5621                currentMoveString[2]++;
5622                break;
5623              case WhiteHSideCastleFR:
5624              case BlackHSideCastleFR:
5625                toX--;
5626                currentMoveString[2]--;
5627                break;
5628              default: ; // nothing to do, but suppresses warning of pedantic compilers
5629            }
5630         }
5631         hintRequested = FALSE;
5632         lastHint[0] = NULLCHAR;
5633         bookRequested = FALSE;
5634         /* Program may be pondering now */
5635         cps->maybeThinking = TRUE;
5636         if (cps->sendTime == 2) cps->sendTime = 1;
5637         if (cps->offeredDraw) cps->offeredDraw--;
5638
5639 #if ZIPPY
5640         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5641             first.initDone) {
5642           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5643           ics_user_moved = 1;
5644           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5645                 char buf[3*MSG_SIZ];
5646
5647                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5648                         programStats.score / 100.,
5649                         programStats.depth,
5650                         programStats.time / 100.,
5651                         u64ToDouble(programStats.nodes),
5652                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5653                         programStats.movelist);
5654                 SendToICS(buf);
5655           }
5656         }
5657 #endif
5658         /* currentMoveString is set as a side-effect of ParseOneMove */
5659         strcpy(machineMove, currentMoveString);
5660         strcat(machineMove, "\n");
5661         strcpy(moveList[forwardMostMove], machineMove);
5662
5663         /* [AS] Save move info and clear stats for next move */
5664         pvInfoList[ forwardMostMove ].score = programStats.score;
5665         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5666         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5667         ClearProgramStats();
5668         thinkOutput[0] = NULLCHAR;
5669         hiddenThinkOutputState = 0;
5670
5671         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5672
5673         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5674         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5675             int count = 0;
5676
5677             while( count < adjudicateLossPlies ) {
5678                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5679
5680                 if( count & 1 ) {
5681                     score = -score; /* Flip score for winning side */
5682                 }
5683
5684                 if( score > adjudicateLossThreshold ) {
5685                     break;
5686                 }
5687
5688                 count++;
5689             }
5690
5691             if( count >= adjudicateLossPlies ) {
5692                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5693
5694                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5695                     "Xboard adjudication", 
5696                     GE_XBOARD );
5697
5698                 return;
5699             }
5700         }
5701
5702         if( gameMode == TwoMachinesPlay ) {
5703           // [HGM] some adjudications useful with buggy engines
5704             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5705           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5706
5707
5708             if( appData.testLegality )
5709             {   /* [HGM] Some more adjudications for obstinate engines */
5710                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5711                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5712                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5713                 static int moveCount = 6;
5714                 ChessMove result;
5715                 char *reason = NULL;
5716
5717                 /* Count what is on board. */
5718                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5719                 {   ChessSquare p = boards[forwardMostMove][i][j];
5720                     int m=i;
5721
5722                     switch((int) p)
5723                     {   /* count B,N,R and other of each side */
5724                         case WhiteKing:
5725                         case BlackKing:
5726                              NrK++; break; // [HGM] atomic: count Kings
5727                         case WhiteKnight:
5728                              NrWN++; break;
5729                         case WhiteBishop:
5730                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5731                              bishopsColor |= 1 << ((i^j)&1);
5732                              NrWB++; break;
5733                         case BlackKnight:
5734                              NrBN++; break;
5735                         case BlackBishop:
5736                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5737                              bishopsColor |= 1 << ((i^j)&1);
5738                              NrBB++; break;
5739                         case WhiteRook:
5740                              NrWR++; break;
5741                         case BlackRook:
5742                              NrBR++; break;
5743                         case WhiteQueen:
5744                              NrWQ++; break;
5745                         case BlackQueen:
5746                              NrBQ++; break;
5747                         case EmptySquare: 
5748                              break;
5749                         case BlackPawn:
5750                              m = 7-i;
5751                         case WhitePawn:
5752                              PawnAdvance += m; NrPawns++;
5753                     }
5754                     NrPieces += (p != EmptySquare);
5755                     NrW += ((int)p < (int)BlackPawn);
5756                     if(gameInfo.variant == VariantXiangqi && 
5757                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5758                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5759                         NrW -= ((int)p < (int)BlackPawn);
5760                     }
5761                 }
5762
5763                 /* Some material-based adjudications that have to be made before stalemate test */
5764                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5765                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5766                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5767                      if(appData.checkMates) {
5768                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5769                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5770                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5771                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5772                          return;
5773                      }
5774                 }
5775
5776                 /* Bare King in Shatranj (loses) or Losers (wins) */
5777                 if( NrW == 1 || NrPieces - NrW == 1) {
5778                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5779                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5780                      if(appData.checkMates) {
5781                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5782                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5783                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5784                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5785                          return;
5786                      }
5787                   } else
5788                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5789                   {    /* bare King */
5790                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5791                         if(appData.checkMates) {
5792                             /* but only adjudicate if adjudication enabled */
5793                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5794                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5795                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5796                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5797                             return;
5798                         }
5799                   }
5800                 } else bare = 1;
5801
5802
5803             // don't wait for engine to announce game end if we can judge ourselves
5804             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5805                                        castlingRights[forwardMostMove]) ) {
5806               case MT_CHECK:
5807                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5808                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5809                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5810                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5811                             checkCnt++;
5812                         if(checkCnt >= 2) {
5813                             reason = "Xboard adjudication: 3rd check";
5814                             epStatus[forwardMostMove] = EP_CHECKMATE;
5815                             break;
5816                         }
5817                     }
5818                 }
5819               case MT_NONE:
5820               default:
5821                 break;
5822               case MT_STALEMATE:
5823               case MT_STAINMATE:
5824                 reason = "Xboard adjudication: Stalemate";
5825                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5826                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5827                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5828                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5829                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5830                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5831                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5832                                                                         EP_CHECKMATE : EP_WINS);
5833                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5834                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5835                 }
5836                 break;
5837               case MT_CHECKMATE:
5838                 reason = "Xboard adjudication: Checkmate";
5839                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5840                 break;
5841             }
5842
5843                 switch(i = epStatus[forwardMostMove]) {
5844                     case EP_STALEMATE:
5845                         result = GameIsDrawn; break;
5846                     case EP_CHECKMATE:
5847                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5848                     case EP_WINS:
5849                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5850                     default:
5851                         result = (ChessMove) 0;
5852                 }
5853                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5854                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5855                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5856                     GameEnds( result, reason, GE_XBOARD );
5857                     return;
5858                 }
5859
5860                 /* Next absolutely insufficient mating material. */
5861                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5862                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5863                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5864                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5865                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5866
5867                      /* always flag draws, for judging claims */
5868                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5869
5870                      if(appData.materialDraws) {
5871                          /* but only adjudicate them if adjudication enabled */
5872                          SendToProgram("force\n", cps->other); // suppress reply
5873                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5874                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5875                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5876                          return;
5877                      }
5878                 }
5879
5880                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5881                 if(NrPieces == 4 && 
5882                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5883                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5884                    || NrWN==2 || NrBN==2     /* KNNK */
5885                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5886                   ) ) {
5887                      if(--moveCount < 0 && appData.trivialDraws)
5888                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5889                           SendToProgram("force\n", cps->other); // suppress reply
5890                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5891                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5892                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5893                           return;
5894                      }
5895                 } else moveCount = 6;
5896             }
5897           }
5898 #if 1
5899     if (appData.debugMode) { int i;
5900       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5901               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5902               appData.drawRepeats);
5903       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5904            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5905
5906     }
5907 #endif
5908                 /* Check for rep-draws */
5909                 count = 0;
5910                 for(k = forwardMostMove-2;
5911                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5912                         epStatus[k] < EP_UNKNOWN &&
5913                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5914                     k-=2)
5915                 {   int rights=0;
5916 #if 0
5917     if (appData.debugMode) {
5918       fprintf(debugFP, " loop\n");
5919     }
5920 #endif
5921                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5922 #if 0
5923     if (appData.debugMode) {
5924       fprintf(debugFP, "match\n");
5925     }
5926 #endif
5927                         /* compare castling rights */
5928                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5929                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5930                                 rights++; /* King lost rights, while rook still had them */
5931                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5932                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5933                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5934                                    rights++; /* but at least one rook lost them */
5935                         }
5936                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5937                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5938                                 rights++; 
5939                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5940                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5941                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5942                                    rights++;
5943                         }
5944 #if 0
5945     if (appData.debugMode) {
5946       for(i=0; i<nrCastlingRights; i++)
5947       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5948     }
5949
5950     if (appData.debugMode) {
5951       fprintf(debugFP, " %d %d\n", rights, k);
5952     }
5953 #endif
5954                         if( rights == 0 && ++count > appData.drawRepeats-2
5955                             && appData.drawRepeats > 1) {
5956                              /* adjudicate after user-specified nr of repeats */
5957                              SendToProgram("force\n", cps->other); // suppress reply
5958                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5959                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5960                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5961                                 // [HGM] xiangqi: check for forbidden perpetuals
5962                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5963                                 for(m=forwardMostMove; m>k; m-=2) {
5964                                     if(MateTest(boards[m], PosFlags(m), 
5965                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5966                                         ourPerpetual = 0; // the current mover did not always check
5967                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5968                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5969                                         hisPerpetual = 0; // the opponent did not always check
5970                                 }
5971                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5972                                                                         ourPerpetual, hisPerpetual);
5973                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5974                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5975                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5976                                     return;
5977                                 }
5978                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5979                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5980                                 // Now check for perpetual chases
5981                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5982                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5983                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5984                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5985                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5986                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5987                                         return;
5988                                     }
5989                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5990                                         break; // Abort repetition-checking loop.
5991                                 }
5992                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5993                              }
5994                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5995                              return;
5996                         }
5997                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5998                              epStatus[forwardMostMove] = EP_REP_DRAW;
5999                     }
6000                 }
6001
6002                 /* Now we test for 50-move draws. Determine ply count */
6003                 count = forwardMostMove;
6004                 /* look for last irreversble move */
6005                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6006                     count--;
6007                 /* if we hit starting position, add initial plies */
6008                 if( count == backwardMostMove )
6009                     count -= initialRulePlies;
6010                 count = forwardMostMove - count; 
6011                 if( count >= 100)
6012                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6013                          /* this is used to judge if draw claims are legal */
6014                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6015                          SendToProgram("force\n", cps->other); // suppress reply
6016                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6017                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6018                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6019                          return;
6020                 }
6021
6022                 /* if draw offer is pending, treat it as a draw claim
6023                  * when draw condition present, to allow engines a way to
6024                  * claim draws before making their move to avoid a race
6025                  * condition occurring after their move
6026                  */
6027                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6028                          char *p = NULL;
6029                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6030                              p = "Draw claim: 50-move rule";
6031                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6032                              p = "Draw claim: 3-fold repetition";
6033                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6034                              p = "Draw claim: insufficient mating material";
6035                          if( p != NULL ) {
6036                              SendToProgram("force\n", cps->other); // suppress reply
6037                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6038                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6039                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040                              return;
6041                          }
6042                 }
6043
6044
6045                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6046                     SendToProgram("force\n", cps->other); // suppress reply
6047                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6048                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6049
6050                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6051
6052                     return;
6053                 }
6054         }
6055
6056         bookHit = NULL;
6057         if (gameMode == TwoMachinesPlay) {
6058             /* [HGM] relaying draw offers moved to after reception of move */
6059             /* and interpreting offer as claim if it brings draw condition */
6060             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6061                 SendToProgram("draw\n", cps->other);
6062             }
6063             if (cps->other->sendTime) {
6064                 SendTimeRemaining(cps->other,
6065                                   cps->other->twoMachinesColor[0] == 'w');
6066             }
6067             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6068             if (firstMove && !bookHit) {
6069                 firstMove = FALSE;
6070                 if (cps->other->useColors) {
6071                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6072                 }
6073                 SendToProgram("go\n", cps->other);
6074             }
6075             cps->other->maybeThinking = TRUE;
6076         }
6077
6078         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6079         
6080         if (!pausing && appData.ringBellAfterMoves) {
6081             RingBell();
6082         }
6083
6084         /* 
6085          * Reenable menu items that were disabled while
6086          * machine was thinking
6087          */
6088         if (gameMode != TwoMachinesPlay)
6089             SetUserThinkingEnables();
6090
6091         // [HGM] book: after book hit opponent has received move and is now in force mode
6092         // force the book reply into it, and then fake that it outputted this move by jumping
6093         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6094         if(bookHit) {
6095                 static char bookMove[MSG_SIZ]; // a bit generous?
6096
6097                 strcpy(bookMove, "move ");
6098                 strcat(bookMove, bookHit);
6099                 message = bookMove;
6100                 cps = cps->other;
6101                 programStats.nodes = programStats.depth = programStats.time = 
6102                 programStats.score = programStats.got_only_move = 0;
6103                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6104
6105                 if(cps->lastPing != cps->lastPong) {
6106                     savedMessage = message; // args for deferred call
6107                     savedState = cps;
6108                     ScheduleDelayedEvent(DeferredBookMove, 10);
6109                     return;
6110                 }
6111                 goto FakeBookMove;
6112         }
6113
6114         return;
6115     }
6116
6117     /* Set special modes for chess engines.  Later something general
6118      *  could be added here; for now there is just one kludge feature,
6119      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6120      *  when "xboard" is given as an interactive command.
6121      */
6122     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6123         cps->useSigint = FALSE;
6124         cps->useSigterm = FALSE;
6125     }
6126
6127     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6128      * want this, I was asked to put it in, and obliged.
6129      */
6130     if (!strncmp(message, "setboard ", 9)) {
6131         Board initial_position; int i;
6132
6133         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6134
6135         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6136             DisplayError(_("Bad FEN received from engine"), 0);
6137             return ;
6138         } else {
6139            Reset(FALSE, FALSE);
6140            CopyBoard(boards[0], initial_position);
6141            initialRulePlies = FENrulePlies;
6142            epStatus[0] = FENepStatus;
6143            for( i=0; i<nrCastlingRights; i++ )
6144                 castlingRights[0][i] = FENcastlingRights[i];
6145            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6146            else gameMode = MachinePlaysBlack;                 
6147            DrawPosition(FALSE, boards[currentMove]);
6148         }
6149         return;
6150     }
6151
6152     /*
6153      * Look for communication commands
6154      */
6155     if (!strncmp(message, "telluser ", 9)) {
6156         DisplayNote(message + 9);
6157         return;
6158     }
6159     if (!strncmp(message, "tellusererror ", 14)) {
6160         DisplayError(message + 14, 0);
6161         return;
6162     }
6163     if (!strncmp(message, "tellopponent ", 13)) {
6164       if (appData.icsActive) {
6165         if (loggedOn) {
6166           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6167           SendToICS(buf1);
6168         }
6169       } else {
6170         DisplayNote(message + 13);
6171       }
6172       return;
6173     }
6174     if (!strncmp(message, "tellothers ", 11)) {
6175       if (appData.icsActive) {
6176         if (loggedOn) {
6177           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6178           SendToICS(buf1);
6179         }
6180       }
6181       return;
6182     }
6183     if (!strncmp(message, "tellall ", 8)) {
6184       if (appData.icsActive) {
6185         if (loggedOn) {
6186           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6187           SendToICS(buf1);
6188         }
6189       } else {
6190         DisplayNote(message + 8);
6191       }
6192       return;
6193     }
6194     if (strncmp(message, "warning", 7) == 0) {
6195         /* Undocumented feature, use tellusererror in new code */
6196         DisplayError(message, 0);
6197         return;
6198     }
6199     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6200         strcpy(realname, cps->tidy);
6201         strcat(realname, " query");
6202         AskQuestion(realname, buf2, buf1, cps->pr);
6203         return;
6204     }
6205     /* Commands from the engine directly to ICS.  We don't allow these to be 
6206      *  sent until we are logged on. Crafty kibitzes have been known to 
6207      *  interfere with the login process.
6208      */
6209     if (loggedOn) {
6210         if (!strncmp(message, "tellics ", 8)) {
6211             SendToICS(message + 8);
6212             SendToICS("\n");
6213             return;
6214         }
6215         if (!strncmp(message, "tellicsnoalias ", 15)) {
6216             SendToICS(ics_prefix);
6217             SendToICS(message + 15);
6218             SendToICS("\n");
6219             return;
6220         }
6221         /* The following are for backward compatibility only */
6222         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6223             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6224             SendToICS(ics_prefix);
6225             SendToICS(message);
6226             SendToICS("\n");
6227             return;
6228         }
6229     }
6230     if (strncmp(message, "feature ", 8) == 0) {
6231       ParseFeatures(message+8, cps);
6232     }
6233     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6234         return;
6235     }
6236     /*
6237      * If the move is illegal, cancel it and redraw the board.
6238      * Also deal with other error cases.  Matching is rather loose
6239      * here to accommodate engines written before the spec.
6240      */
6241     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6242         strncmp(message, "Error", 5) == 0) {
6243         if (StrStr(message, "name") || 
6244             StrStr(message, "rating") || StrStr(message, "?") ||
6245             StrStr(message, "result") || StrStr(message, "board") ||
6246             StrStr(message, "bk") || StrStr(message, "computer") ||
6247             StrStr(message, "variant") || StrStr(message, "hint") ||
6248             StrStr(message, "random") || StrStr(message, "depth") ||
6249             StrStr(message, "accepted")) {
6250             return;
6251         }
6252         if (StrStr(message, "protover")) {
6253           /* Program is responding to input, so it's apparently done
6254              initializing, and this error message indicates it is
6255              protocol version 1.  So we don't need to wait any longer
6256              for it to initialize and send feature commands. */
6257           FeatureDone(cps, 1);
6258           cps->protocolVersion = 1;
6259           return;
6260         }
6261         cps->maybeThinking = FALSE;
6262
6263         if (StrStr(message, "draw")) {
6264             /* Program doesn't have "draw" command */
6265             cps->sendDrawOffers = 0;
6266             return;
6267         }
6268         if (cps->sendTime != 1 &&
6269             (StrStr(message, "time") || StrStr(message, "otim"))) {
6270           /* Program apparently doesn't have "time" or "otim" command */
6271           cps->sendTime = 0;
6272           return;
6273         }
6274         if (StrStr(message, "analyze")) {
6275             cps->analysisSupport = FALSE;
6276             cps->analyzing = FALSE;
6277             Reset(FALSE, TRUE);
6278             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6279             DisplayError(buf2, 0);
6280             return;
6281         }
6282         if (StrStr(message, "(no matching move)st")) {
6283           /* Special kludge for GNU Chess 4 only */
6284           cps->stKludge = TRUE;
6285           SendTimeControl(cps, movesPerSession, timeControl,
6286                           timeIncrement, appData.searchDepth,
6287                           searchTime);
6288           return;
6289         }
6290         if (StrStr(message, "(no matching move)sd")) {
6291           /* Special kludge for GNU Chess 4 only */
6292           cps->sdKludge = TRUE;
6293           SendTimeControl(cps, movesPerSession, timeControl,
6294                           timeIncrement, appData.searchDepth,
6295                           searchTime);
6296           return;
6297         }
6298         if (!StrStr(message, "llegal")) {
6299             return;
6300         }
6301         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6302             gameMode == IcsIdle) return;
6303         if (forwardMostMove <= backwardMostMove) return;
6304 #if 0
6305         /* Following removed: it caused a bug where a real illegal move
6306            message in analyze mored would be ignored. */
6307         if (cps == &first && programStats.ok_to_send == 0) {
6308             /* Bogus message from Crafty responding to "."  This filtering
6309                can miss some of the bad messages, but fortunately the bug 
6310                is fixed in current Crafty versions, so it doesn't matter. */
6311             return;
6312         }
6313 #endif
6314         if (pausing) PauseEvent();
6315         if (gameMode == PlayFromGameFile) {
6316             /* Stop reading this game file */
6317             gameMode = EditGame;
6318             ModeHighlight();
6319         }
6320         currentMove = --forwardMostMove;
6321         DisplayMove(currentMove-1); /* before DisplayMoveError */
6322         SwitchClocks();
6323         DisplayBothClocks();
6324         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6325                 parseList[currentMove], cps->which);
6326         DisplayMoveError(buf1);
6327         DrawPosition(FALSE, boards[currentMove]);
6328
6329         /* [HGM] illegal-move claim should forfeit game when Xboard */
6330         /* only passes fully legal moves                            */
6331         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6332             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6333                                 "False illegal-move claim", GE_XBOARD );
6334         }
6335         return;
6336     }
6337     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6338         /* Program has a broken "time" command that
6339            outputs a string not ending in newline.
6340            Don't use it. */
6341         cps->sendTime = 0;
6342     }
6343     
6344     /*
6345      * If chess program startup fails, exit with an error message.
6346      * Attempts to recover here are futile.
6347      */
6348     if ((StrStr(message, "unknown host") != NULL)
6349         || (StrStr(message, "No remote directory") != NULL)
6350         || (StrStr(message, "not found") != NULL)
6351         || (StrStr(message, "No such file") != NULL)
6352         || (StrStr(message, "can't alloc") != NULL)
6353         || (StrStr(message, "Permission denied") != NULL)) {
6354
6355         cps->maybeThinking = FALSE;
6356         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6357                 cps->which, cps->program, cps->host, message);
6358         RemoveInputSource(cps->isr);
6359         DisplayFatalError(buf1, 0, 1);
6360         return;
6361     }
6362     
6363     /* 
6364      * Look for hint output
6365      */
6366     if (sscanf(message, "Hint: %s", buf1) == 1) {
6367         if (cps == &first && hintRequested) {
6368             hintRequested = FALSE;
6369             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6370                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6371                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6372                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6373                                     fromY, fromX, toY, toX, promoChar, buf1);
6374                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6375                 DisplayInformation(buf2);
6376             } else {
6377                 /* Hint move could not be parsed!? */
6378               snprintf(buf2, sizeof(buf2),
6379                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6380                         buf1, cps->which);
6381                 DisplayError(buf2, 0);
6382             }
6383         } else {
6384             strcpy(lastHint, buf1);
6385         }
6386         return;
6387     }
6388
6389     /*
6390      * Ignore other messages if game is not in progress
6391      */
6392     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6393         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6394
6395     /*
6396      * look for win, lose, draw, or draw offer
6397      */
6398     if (strncmp(message, "1-0", 3) == 0) {
6399         char *p, *q, *r = "";
6400         p = strchr(message, '{');
6401         if (p) {
6402             q = strchr(p, '}');
6403             if (q) {
6404                 *q = NULLCHAR;
6405                 r = p + 1;
6406             }
6407         }
6408         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6409         return;
6410     } else if (strncmp(message, "0-1", 3) == 0) {
6411         char *p, *q, *r = "";
6412         p = strchr(message, '{');
6413         if (p) {
6414             q = strchr(p, '}');
6415             if (q) {
6416                 *q = NULLCHAR;
6417                 r = p + 1;
6418             }
6419         }
6420         /* Kludge for Arasan 4.1 bug */
6421         if (strcmp(r, "Black resigns") == 0) {
6422             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6423             return;
6424         }
6425         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6426         return;
6427     } else if (strncmp(message, "1/2", 3) == 0) {
6428         char *p, *q, *r = "";
6429         p = strchr(message, '{');
6430         if (p) {
6431             q = strchr(p, '}');
6432             if (q) {
6433                 *q = NULLCHAR;
6434                 r = p + 1;
6435             }
6436         }
6437             
6438         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6439         return;
6440
6441     } else if (strncmp(message, "White resign", 12) == 0) {
6442         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6443         return;
6444     } else if (strncmp(message, "Black resign", 12) == 0) {
6445         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "White matches", 13) == 0 ||
6448                strncmp(message, "Black matches", 13) == 0   ) {
6449         /* [HGM] ignore GNUShogi noises */
6450         return;
6451     } else if (strncmp(message, "White", 5) == 0 &&
6452                message[5] != '(' &&
6453                StrStr(message, "Black") == NULL) {
6454         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6455         return;
6456     } else if (strncmp(message, "Black", 5) == 0 &&
6457                message[5] != '(') {
6458         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6459         return;
6460     } else if (strcmp(message, "resign") == 0 ||
6461                strcmp(message, "computer resigns") == 0) {
6462         switch (gameMode) {
6463           case MachinePlaysBlack:
6464           case IcsPlayingBlack:
6465             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6466             break;
6467           case MachinePlaysWhite:
6468           case IcsPlayingWhite:
6469             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6470             break;
6471           case TwoMachinesPlay:
6472             if (cps->twoMachinesColor[0] == 'w')
6473               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6474             else
6475               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6476             break;
6477           default:
6478             /* can't happen */
6479             break;
6480         }
6481         return;
6482     } else if (strncmp(message, "opponent mates", 14) == 0) {
6483         switch (gameMode) {
6484           case MachinePlaysBlack:
6485           case IcsPlayingBlack:
6486             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6487             break;
6488           case MachinePlaysWhite:
6489           case IcsPlayingWhite:
6490             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6491             break;
6492           case TwoMachinesPlay:
6493             if (cps->twoMachinesColor[0] == 'w')
6494               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6495             else
6496               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6497             break;
6498           default:
6499             /* can't happen */
6500             break;
6501         }
6502         return;
6503     } else if (strncmp(message, "computer mates", 14) == 0) {
6504         switch (gameMode) {
6505           case MachinePlaysBlack:
6506           case IcsPlayingBlack:
6507             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6508             break;
6509           case MachinePlaysWhite:
6510           case IcsPlayingWhite:
6511             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6512             break;
6513           case TwoMachinesPlay:
6514             if (cps->twoMachinesColor[0] == 'w')
6515               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6516             else
6517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6518             break;
6519           default:
6520             /* can't happen */
6521             break;
6522         }
6523         return;
6524     } else if (strncmp(message, "checkmate", 9) == 0) {
6525         if (WhiteOnMove(forwardMostMove)) {
6526             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6527         } else {
6528             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6529         }
6530         return;
6531     } else if (strstr(message, "Draw") != NULL ||
6532                strstr(message, "game is a draw") != NULL) {
6533         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6534         return;
6535     } else if (strstr(message, "offer") != NULL &&
6536                strstr(message, "draw") != NULL) {
6537 #if ZIPPY
6538         if (appData.zippyPlay && first.initDone) {
6539             /* Relay offer to ICS */
6540             SendToICS(ics_prefix);
6541             SendToICS("draw\n");
6542         }
6543 #endif
6544         cps->offeredDraw = 2; /* valid until this engine moves twice */
6545         if (gameMode == TwoMachinesPlay) {
6546             if (cps->other->offeredDraw) {
6547                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6548             /* [HGM] in two-machine mode we delay relaying draw offer      */
6549             /* until after we also have move, to see if it is really claim */
6550             }
6551 #if 0
6552               else {
6553                 if (cps->other->sendDrawOffers) {
6554                     SendToProgram("draw\n", cps->other);
6555                 }
6556             }
6557 #endif
6558         } else if (gameMode == MachinePlaysWhite ||
6559                    gameMode == MachinePlaysBlack) {
6560           if (userOfferedDraw) {
6561             DisplayInformation(_("Machine accepts your draw offer"));
6562             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6563           } else {
6564             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6565           }
6566         }
6567     }
6568
6569     
6570     /*
6571      * Look for thinking output
6572      */
6573     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6574           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6575                                 ) {
6576         int plylev, mvleft, mvtot, curscore, time;
6577         char mvname[MOVE_LEN];
6578         u64 nodes; // [DM]
6579         char plyext;
6580         int ignore = FALSE;
6581         int prefixHint = FALSE;
6582         mvname[0] = NULLCHAR;
6583
6584         switch (gameMode) {
6585           case MachinePlaysBlack:
6586           case IcsPlayingBlack:
6587             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6588             break;
6589           case MachinePlaysWhite:
6590           case IcsPlayingWhite:
6591             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6592             break;
6593           case AnalyzeMode:
6594           case AnalyzeFile:
6595             break;
6596           case IcsObserving: /* [DM] icsEngineAnalyze */
6597             if (!appData.icsEngineAnalyze) ignore = TRUE;
6598             break;
6599           case TwoMachinesPlay:
6600             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6601                 ignore = TRUE;
6602             }
6603             break;
6604           default:
6605             ignore = TRUE;
6606             break;
6607         }
6608
6609         if (!ignore) {
6610             buf1[0] = NULLCHAR;
6611             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6612                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6613
6614                 if (plyext != ' ' && plyext != '\t') {
6615                     time *= 100;
6616                 }
6617
6618                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6619                 if( cps->scoreIsAbsolute && 
6620                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6621                 {
6622                     curscore = -curscore;
6623                 }
6624
6625
6626                 programStats.depth = plylev;
6627                 programStats.nodes = nodes;
6628                 programStats.time = time;
6629                 programStats.score = curscore;
6630                 programStats.got_only_move = 0;
6631
6632                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6633                         int ticklen;
6634
6635                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6636                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6637                         if(WhiteOnMove(forwardMostMove)) 
6638                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6639                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6640                 }
6641
6642                 /* Buffer overflow protection */
6643                 if (buf1[0] != NULLCHAR) {
6644                     if (strlen(buf1) >= sizeof(programStats.movelist)
6645                         && appData.debugMode) {
6646                         fprintf(debugFP,
6647                                 "PV is too long; using the first %d bytes.\n",
6648                                 sizeof(programStats.movelist) - 1);
6649                     }
6650
6651                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6652                 } else {
6653                     sprintf(programStats.movelist, " no PV\n");
6654                 }
6655
6656                 if (programStats.seen_stat) {
6657                     programStats.ok_to_send = 1;
6658                 }
6659
6660                 if (strchr(programStats.movelist, '(') != NULL) {
6661                     programStats.line_is_book = 1;
6662                     programStats.nr_moves = 0;
6663                     programStats.moves_left = 0;
6664                 } else {
6665                     programStats.line_is_book = 0;
6666                 }
6667
6668                 SendProgramStatsToFrontend( cps, &programStats );
6669
6670                 /* 
6671                     [AS] Protect the thinkOutput buffer from overflow... this
6672                     is only useful if buf1 hasn't overflowed first!
6673                 */
6674                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6675                         plylev, 
6676                         (gameMode == TwoMachinesPlay ?
6677                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6678                         ((double) curscore) / 100.0,
6679                         prefixHint ? lastHint : "",
6680                         prefixHint ? " " : "" );
6681
6682                 if( buf1[0] != NULLCHAR ) {
6683                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6684
6685                     if( strlen(buf1) > max_len ) {
6686                         if( appData.debugMode) {
6687                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6688                         }
6689                         buf1[max_len+1] = '\0';
6690                     }
6691
6692                     strcat( thinkOutput, buf1 );
6693                 }
6694
6695                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6696                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6697                     DisplayMove(currentMove - 1);
6698                     DisplayAnalysis();
6699                 }
6700                 return;
6701
6702             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6703                 /* crafty (9.25+) says "(only move) <move>"
6704                  * if there is only 1 legal move
6705                  */
6706                 sscanf(p, "(only move) %s", buf1);
6707                 sprintf(thinkOutput, "%s (only move)", buf1);
6708                 sprintf(programStats.movelist, "%s (only move)", buf1);
6709                 programStats.depth = 1;
6710                 programStats.nr_moves = 1;
6711                 programStats.moves_left = 1;
6712                 programStats.nodes = 1;
6713                 programStats.time = 1;
6714                 programStats.got_only_move = 1;
6715
6716                 /* Not really, but we also use this member to
6717                    mean "line isn't going to change" (Crafty
6718                    isn't searching, so stats won't change) */
6719                 programStats.line_is_book = 1;
6720
6721                 SendProgramStatsToFrontend( cps, &programStats );
6722                 
6723                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6724                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6725                     DisplayMove(currentMove - 1);
6726                     DisplayAnalysis();
6727                 }
6728                 return;
6729             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6730                               &time, &nodes, &plylev, &mvleft,
6731                               &mvtot, mvname) >= 5) {
6732                 /* The stat01: line is from Crafty (9.29+) in response
6733                    to the "." command */
6734                 programStats.seen_stat = 1;
6735                 cps->maybeThinking = TRUE;
6736
6737                 if (programStats.got_only_move || !appData.periodicUpdates)
6738                   return;
6739
6740                 programStats.depth = plylev;
6741                 programStats.time = time;
6742                 programStats.nodes = nodes;
6743                 programStats.moves_left = mvleft;
6744                 programStats.nr_moves = mvtot;
6745                 strcpy(programStats.move_name, mvname);
6746                 programStats.ok_to_send = 1;
6747                 programStats.movelist[0] = '\0';
6748
6749                 SendProgramStatsToFrontend( cps, &programStats );
6750
6751                 DisplayAnalysis();
6752                 return;
6753
6754             } else if (strncmp(message,"++",2) == 0) {
6755                 /* Crafty 9.29+ outputs this */
6756                 programStats.got_fail = 2;
6757                 return;
6758
6759             } else if (strncmp(message,"--",2) == 0) {
6760                 /* Crafty 9.29+ outputs this */
6761                 programStats.got_fail = 1;
6762                 return;
6763
6764             } else if (thinkOutput[0] != NULLCHAR &&
6765                        strncmp(message, "    ", 4) == 0) {
6766                 unsigned message_len;
6767
6768                 p = message;
6769                 while (*p && *p == ' ') p++;
6770
6771                 message_len = strlen( p );
6772
6773                 /* [AS] Avoid buffer overflow */
6774                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6775                     strcat(thinkOutput, " ");
6776                     strcat(thinkOutput, p);
6777                 }
6778
6779                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6780                     strcat(programStats.movelist, " ");
6781                     strcat(programStats.movelist, p);
6782                 }
6783
6784                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6785                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6786                     DisplayMove(currentMove - 1);
6787                     DisplayAnalysis();
6788                 }
6789                 return;
6790             }
6791         }
6792         else {
6793             buf1[0] = NULLCHAR;
6794
6795             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6796                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6797             {
6798                 ChessProgramStats cpstats;
6799
6800                 if (plyext != ' ' && plyext != '\t') {
6801                     time *= 100;
6802                 }
6803
6804                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6805                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6806                     curscore = -curscore;
6807                 }
6808
6809                 cpstats.depth = plylev;
6810                 cpstats.nodes = nodes;
6811                 cpstats.time = time;
6812                 cpstats.score = curscore;
6813                 cpstats.got_only_move = 0;
6814                 cpstats.movelist[0] = '\0';
6815
6816                 if (buf1[0] != NULLCHAR) {
6817                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6818                 }
6819
6820                 cpstats.ok_to_send = 0;
6821                 cpstats.line_is_book = 0;
6822                 cpstats.nr_moves = 0;
6823                 cpstats.moves_left = 0;
6824
6825                 SendProgramStatsToFrontend( cps, &cpstats );
6826             }
6827         }
6828     }
6829 }
6830
6831
6832 /* Parse a game score from the character string "game", and
6833    record it as the history of the current game.  The game
6834    score is NOT assumed to start from the standard position. 
6835    The display is not updated in any way.
6836    */
6837 void
6838 ParseGameHistory(game)
6839      char *game;
6840 {
6841     ChessMove moveType;
6842     int fromX, fromY, toX, toY, boardIndex;
6843     char promoChar;
6844     char *p, *q;
6845     char buf[MSG_SIZ];
6846
6847     if (appData.debugMode)
6848       fprintf(debugFP, "Parsing game history: %s\n", game);
6849
6850     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6851     gameInfo.site = StrSave(appData.icsHost);
6852     gameInfo.date = PGNDate();
6853     gameInfo.round = StrSave("-");
6854
6855     /* Parse out names of players */
6856     while (*game == ' ') game++;
6857     p = buf;
6858     while (*game != ' ') *p++ = *game++;
6859     *p = NULLCHAR;
6860     gameInfo.white = StrSave(buf);
6861     while (*game == ' ') game++;
6862     p = buf;
6863     while (*game != ' ' && *game != '\n') *p++ = *game++;
6864     *p = NULLCHAR;
6865     gameInfo.black = StrSave(buf);
6866
6867     /* Parse moves */
6868     boardIndex = blackPlaysFirst ? 1 : 0;
6869     yynewstr(game);
6870     for (;;) {
6871         yyboardindex = boardIndex;
6872         moveType = (ChessMove) yylex();
6873         switch (moveType) {
6874           case IllegalMove:             /* maybe suicide chess, etc. */
6875   if (appData.debugMode) {
6876     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6877     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6878     setbuf(debugFP, NULL);
6879   }
6880           case WhitePromotionChancellor:
6881           case BlackPromotionChancellor:
6882           case WhitePromotionArchbishop:
6883           case BlackPromotionArchbishop:
6884           case WhitePromotionQueen:
6885           case BlackPromotionQueen:
6886           case WhitePromotionRook:
6887           case BlackPromotionRook:
6888           case WhitePromotionBishop:
6889           case BlackPromotionBishop:
6890           case WhitePromotionKnight:
6891           case BlackPromotionKnight:
6892           case WhitePromotionKing:
6893           case BlackPromotionKing:
6894           case NormalMove:
6895           case WhiteCapturesEnPassant:
6896           case BlackCapturesEnPassant:
6897           case WhiteKingSideCastle:
6898           case WhiteQueenSideCastle:
6899           case BlackKingSideCastle:
6900           case BlackQueenSideCastle:
6901           case WhiteKingSideCastleWild:
6902           case WhiteQueenSideCastleWild:
6903           case BlackKingSideCastleWild:
6904           case BlackQueenSideCastleWild:
6905           /* PUSH Fabien */
6906           case WhiteHSideCastleFR:
6907           case WhiteASideCastleFR:
6908           case BlackHSideCastleFR:
6909           case BlackASideCastleFR:
6910           /* POP Fabien */
6911             fromX = currentMoveString[0] - AAA;
6912             fromY = currentMoveString[1] - ONE;
6913             toX = currentMoveString[2] - AAA;
6914             toY = currentMoveString[3] - ONE;
6915             promoChar = currentMoveString[4];
6916             break;
6917           case WhiteDrop:
6918           case BlackDrop:
6919             fromX = moveType == WhiteDrop ?
6920               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6921             (int) CharToPiece(ToLower(currentMoveString[0]));
6922             fromY = DROP_RANK;
6923             toX = currentMoveString[2] - AAA;
6924             toY = currentMoveString[3] - ONE;
6925             promoChar = NULLCHAR;
6926             break;
6927           case AmbiguousMove:
6928             /* bug? */
6929             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6930   if (appData.debugMode) {
6931     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6932     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6933     setbuf(debugFP, NULL);
6934   }
6935             DisplayError(buf, 0);
6936             return;
6937           case ImpossibleMove:
6938             /* bug? */
6939             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6940   if (appData.debugMode) {
6941     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6942     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6943     setbuf(debugFP, NULL);
6944   }
6945             DisplayError(buf, 0);
6946             return;
6947           case (ChessMove) 0:   /* end of file */
6948             if (boardIndex < backwardMostMove) {
6949                 /* Oops, gap.  How did that happen? */
6950                 DisplayError(_("Gap in move list"), 0);
6951                 return;
6952             }
6953             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6954             if (boardIndex > forwardMostMove) {
6955                 forwardMostMove = boardIndex;
6956             }
6957             return;
6958           case ElapsedTime:
6959             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6960                 strcat(parseList[boardIndex-1], " ");
6961                 strcat(parseList[boardIndex-1], yy_text);
6962             }
6963             continue;
6964           case Comment:
6965           case PGNTag:
6966           case NAG:
6967           default:
6968             /* ignore */
6969             continue;
6970           case WhiteWins:
6971           case BlackWins:
6972           case GameIsDrawn:
6973           case GameUnfinished:
6974             if (gameMode == IcsExamining) {
6975                 if (boardIndex < backwardMostMove) {
6976                     /* Oops, gap.  How did that happen? */
6977                     return;
6978                 }
6979                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6980                 return;
6981             }
6982             gameInfo.result = moveType;
6983             p = strchr(yy_text, '{');
6984             if (p == NULL) p = strchr(yy_text, '(');
6985             if (p == NULL) {
6986                 p = yy_text;
6987                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6988             } else {
6989                 q = strchr(p, *p == '{' ? '}' : ')');
6990                 if (q != NULL) *q = NULLCHAR;
6991                 p++;
6992             }
6993             gameInfo.resultDetails = StrSave(p);
6994             continue;
6995         }
6996         if (boardIndex >= forwardMostMove &&
6997             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6998             backwardMostMove = blackPlaysFirst ? 1 : 0;
6999             return;
7000         }
7001         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7002                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7003                                  parseList[boardIndex]);
7004         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7005         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7006         /* currentMoveString is set as a side-effect of yylex */
7007         strcpy(moveList[boardIndex], currentMoveString);
7008         strcat(moveList[boardIndex], "\n");
7009         boardIndex++;
7010         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7011                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7012         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7013                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7014           case MT_NONE:
7015           case MT_STALEMATE:
7016           default:
7017             break;
7018           case MT_CHECK:
7019             if(gameInfo.variant != VariantShogi)
7020                 strcat(parseList[boardIndex - 1], "+");
7021             break;
7022           case MT_CHECKMATE:
7023           case MT_STAINMATE:
7024             strcat(parseList[boardIndex - 1], "#");
7025             break;
7026         }
7027     }
7028 }
7029
7030
7031 /* Apply a move to the given board  */
7032 void
7033 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7034      int fromX, fromY, toX, toY;
7035      int promoChar;
7036      Board board;
7037      char *castling;
7038      char *ep;
7039 {
7040   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7041
7042     /* [HGM] compute & store e.p. status and castling rights for new position */
7043     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7044     { int i;
7045
7046       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7047       oldEP = *ep;
7048       *ep = EP_NONE;
7049
7050       if( board[toY][toX] != EmptySquare ) 
7051            *ep = EP_CAPTURE;  
7052
7053       if( board[fromY][fromX] == WhitePawn ) {
7054            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7055                *ep = EP_PAWN_MOVE;
7056            if( toY-fromY==2) {
7057                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7058                         gameInfo.variant != VariantBerolina || toX < fromX)
7059                       *ep = toX | berolina;
7060                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7061                         gameInfo.variant != VariantBerolina || toX > fromX) 
7062                       *ep = toX;
7063            }
7064       } else 
7065       if( board[fromY][fromX] == BlackPawn ) {
7066            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7067                *ep = EP_PAWN_MOVE; 
7068            if( toY-fromY== -2) {
7069                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7070                         gameInfo.variant != VariantBerolina || toX < fromX)
7071                       *ep = toX | berolina;
7072                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7073                         gameInfo.variant != VariantBerolina || toX > fromX) 
7074                       *ep = toX;
7075            }
7076        }
7077
7078        for(i=0; i<nrCastlingRights; i++) {
7079            if(castling[i] == fromX && castlingRank[i] == fromY ||
7080               castling[i] == toX   && castlingRank[i] == toY   
7081              ) castling[i] = -1; // revoke for moved or captured piece
7082        }
7083
7084     }
7085
7086   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7087   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7088        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7089          
7090   if (fromX == toX && fromY == toY) return;
7091
7092   if (fromY == DROP_RANK) {
7093         /* must be first */
7094         piece = board[toY][toX] = (ChessSquare) fromX;
7095   } else {
7096      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7097      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7098      if(gameInfo.variant == VariantKnightmate)
7099          king += (int) WhiteUnicorn - (int) WhiteKing;
7100
7101     /* Code added by Tord: */
7102     /* FRC castling assumed when king captures friendly rook. */
7103     if (board[fromY][fromX] == WhiteKing &&
7104              board[toY][toX] == WhiteRook) {
7105       board[fromY][fromX] = EmptySquare;
7106       board[toY][toX] = EmptySquare;
7107       if(toX > fromX) {
7108         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7109       } else {
7110         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7111       }
7112     } else if (board[fromY][fromX] == BlackKing &&
7113                board[toY][toX] == BlackRook) {
7114       board[fromY][fromX] = EmptySquare;
7115       board[toY][toX] = EmptySquare;
7116       if(toX > fromX) {
7117         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7118       } else {
7119         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7120       }
7121     /* End of code added by Tord */
7122
7123     } else if (board[fromY][fromX] == king
7124         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7125         && toY == fromY && toX > fromX+1) {
7126         board[fromY][fromX] = EmptySquare;
7127         board[toY][toX] = king;
7128         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7129         board[fromY][BOARD_RGHT-1] = EmptySquare;
7130     } else if (board[fromY][fromX] == king
7131         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7132                && toY == fromY && toX < fromX-1) {
7133         board[fromY][fromX] = EmptySquare;
7134         board[toY][toX] = king;
7135         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7136         board[fromY][BOARD_LEFT] = EmptySquare;
7137     } else if (board[fromY][fromX] == WhitePawn
7138                && toY == BOARD_HEIGHT-1
7139                && gameInfo.variant != VariantXiangqi
7140                ) {
7141         /* white pawn promotion */
7142         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7143         if (board[toY][toX] == EmptySquare) {
7144             board[toY][toX] = WhiteQueen;
7145         }
7146         if(gameInfo.variant==VariantBughouse ||
7147            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7148             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7149         board[fromY][fromX] = EmptySquare;
7150     } else if ((fromY == BOARD_HEIGHT-4)
7151                && (toX != fromX)
7152                && gameInfo.variant != VariantXiangqi
7153                && gameInfo.variant != VariantBerolina
7154                && (board[fromY][fromX] == WhitePawn)
7155                && (board[toY][toX] == EmptySquare)) {
7156         board[fromY][fromX] = EmptySquare;
7157         board[toY][toX] = WhitePawn;
7158         captured = board[toY - 1][toX];
7159         board[toY - 1][toX] = EmptySquare;
7160     } else if ((fromY == BOARD_HEIGHT-4)
7161                && (toX == fromX)
7162                && gameInfo.variant == VariantBerolina
7163                && (board[fromY][fromX] == WhitePawn)
7164                && (board[toY][toX] == EmptySquare)) {
7165         board[fromY][fromX] = EmptySquare;
7166         board[toY][toX] = WhitePawn;
7167         if(oldEP & EP_BEROLIN_A) {
7168                 captured = board[fromY][fromX-1];
7169                 board[fromY][fromX-1] = EmptySquare;
7170         }else{  captured = board[fromY][fromX+1];
7171                 board[fromY][fromX+1] = EmptySquare;
7172         }
7173     } else if (board[fromY][fromX] == king
7174         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7175                && toY == fromY && toX > fromX+1) {
7176         board[fromY][fromX] = EmptySquare;
7177         board[toY][toX] = king;
7178         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7179         board[fromY][BOARD_RGHT-1] = EmptySquare;
7180     } else if (board[fromY][fromX] == king
7181         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7182                && toY == fromY && toX < fromX-1) {
7183         board[fromY][fromX] = EmptySquare;
7184         board[toY][toX] = king;
7185         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7186         board[fromY][BOARD_LEFT] = EmptySquare;
7187     } else if (fromY == 7 && fromX == 3
7188                && board[fromY][fromX] == BlackKing
7189                && toY == 7 && toX == 5) {
7190         board[fromY][fromX] = EmptySquare;
7191         board[toY][toX] = BlackKing;
7192         board[fromY][7] = EmptySquare;
7193         board[toY][4] = BlackRook;
7194     } else if (fromY == 7 && fromX == 3
7195                && board[fromY][fromX] == BlackKing
7196                && toY == 7 && toX == 1) {
7197         board[fromY][fromX] = EmptySquare;
7198         board[toY][toX] = BlackKing;
7199         board[fromY][0] = EmptySquare;
7200         board[toY][2] = BlackRook;
7201     } else if (board[fromY][fromX] == BlackPawn
7202                && toY == 0
7203                && gameInfo.variant != VariantXiangqi
7204                ) {
7205         /* black pawn promotion */
7206         board[0][toX] = CharToPiece(ToLower(promoChar));
7207         if (board[0][toX] == EmptySquare) {
7208             board[0][toX] = BlackQueen;
7209         }
7210         if(gameInfo.variant==VariantBughouse ||
7211            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7212             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7213         board[fromY][fromX] = EmptySquare;
7214     } else if ((fromY == 3)
7215                && (toX != fromX)
7216                && gameInfo.variant != VariantXiangqi
7217                && gameInfo.variant != VariantBerolina
7218                && (board[fromY][fromX] == BlackPawn)
7219                && (board[toY][toX] == EmptySquare)) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = BlackPawn;
7222         captured = board[toY + 1][toX];
7223         board[toY + 1][toX] = EmptySquare;
7224     } else if ((fromY == 3)
7225                && (toX == fromX)
7226                && gameInfo.variant == VariantBerolina
7227                && (board[fromY][fromX] == BlackPawn)
7228                && (board[toY][toX] == EmptySquare)) {
7229         board[fromY][fromX] = EmptySquare;
7230         board[toY][toX] = BlackPawn;
7231         if(oldEP & EP_BEROLIN_A) {
7232                 captured = board[fromY][fromX-1];
7233                 board[fromY][fromX-1] = EmptySquare;
7234         }else{  captured = board[fromY][fromX+1];
7235                 board[fromY][fromX+1] = EmptySquare;
7236         }
7237     } else {
7238         board[toY][toX] = board[fromY][fromX];
7239         board[fromY][fromX] = EmptySquare;
7240     }
7241
7242     /* [HGM] now we promote for Shogi, if needed */
7243     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7244         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7245   }
7246
7247     if (gameInfo.holdingsWidth != 0) {
7248
7249       /* !!A lot more code needs to be written to support holdings  */
7250       /* [HGM] OK, so I have written it. Holdings are stored in the */
7251       /* penultimate board files, so they are automaticlly stored   */
7252       /* in the game history.                                       */
7253       if (fromY == DROP_RANK) {
7254         /* Delete from holdings, by decreasing count */
7255         /* and erasing image if necessary            */
7256         p = (int) fromX;
7257         if(p < (int) BlackPawn) { /* white drop */
7258              p -= (int)WhitePawn;
7259              if(p >= gameInfo.holdingsSize) p = 0;
7260              if(--board[p][BOARD_WIDTH-2] == 0)
7261                   board[p][BOARD_WIDTH-1] = EmptySquare;
7262         } else {                  /* black drop */
7263              p -= (int)BlackPawn;
7264              if(p >= gameInfo.holdingsSize) p = 0;
7265              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7266                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7267         }
7268       }
7269       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7270           && gameInfo.variant != VariantBughouse        ) {
7271         /* [HGM] holdings: Add to holdings, if holdings exist */
7272         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7273                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7274                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7275         }
7276         p = (int) captured;
7277         if (p >= (int) BlackPawn) {
7278           p -= (int)BlackPawn;
7279           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7280                   /* in Shogi restore piece to its original  first */
7281                   captured = (ChessSquare) (DEMOTED captured);
7282                   p = DEMOTED p;
7283           }
7284           p = PieceToNumber((ChessSquare)p);
7285           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7286           board[p][BOARD_WIDTH-2]++;
7287           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7288         } else {
7289           p -= (int)WhitePawn;
7290           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7291                   captured = (ChessSquare) (DEMOTED captured);
7292                   p = DEMOTED p;
7293           }
7294           p = PieceToNumber((ChessSquare)p);
7295           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7296           board[BOARD_HEIGHT-1-p][1]++;
7297           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7298         }
7299       }
7300
7301     } else if (gameInfo.variant == VariantAtomic) {
7302       if (captured != EmptySquare) {
7303         int y, x;
7304         for (y = toY-1; y <= toY+1; y++) {
7305           for (x = toX-1; x <= toX+1; x++) {
7306             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7307                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7308               board[y][x] = EmptySquare;
7309             }
7310           }
7311         }
7312         board[toY][toX] = EmptySquare;
7313       }
7314     }
7315     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7316         /* [HGM] Shogi promotions */
7317         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7318     }
7319
7320     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7321                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7322         // [HGM] superchess: take promotion piece out of holdings
7323         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7324         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7325             if(!--board[k][BOARD_WIDTH-2])
7326                 board[k][BOARD_WIDTH-1] = EmptySquare;
7327         } else {
7328             if(!--board[BOARD_HEIGHT-1-k][1])
7329                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7330         }
7331     }
7332
7333 }
7334
7335 /* Updates forwardMostMove */
7336 void
7337 MakeMove(fromX, fromY, toX, toY, promoChar)
7338      int fromX, fromY, toX, toY;
7339      int promoChar;
7340 {
7341 //    forwardMostMove++; // [HGM] bare: moved downstream
7342
7343     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7344         int timeLeft; static int lastLoadFlag=0; int king, piece;
7345         piece = boards[forwardMostMove][fromY][fromX];
7346         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7347         if(gameInfo.variant == VariantKnightmate)
7348             king += (int) WhiteUnicorn - (int) WhiteKing;
7349         if(forwardMostMove == 0) {
7350             if(blackPlaysFirst) 
7351                 fprintf(serverMoves, "%s;", second.tidy);
7352             fprintf(serverMoves, "%s;", first.tidy);
7353             if(!blackPlaysFirst) 
7354                 fprintf(serverMoves, "%s;", second.tidy);
7355         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7356         lastLoadFlag = loadFlag;
7357         // print base move
7358         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7359         // print castling suffix
7360         if( toY == fromY && piece == king ) {
7361             if(toX-fromX > 1)
7362                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7363             if(fromX-toX >1)
7364                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7365         }
7366         // e.p. suffix
7367         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7368              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7369              boards[forwardMostMove][toY][toX] == EmptySquare
7370              && fromX != toX )
7371                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7372         // promotion suffix
7373         if(promoChar != NULLCHAR)
7374                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7375         if(!loadFlag) {
7376             fprintf(serverMoves, "/%d/%d",
7377                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7378             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7379             else                      timeLeft = blackTimeRemaining/1000;
7380             fprintf(serverMoves, "/%d", timeLeft);
7381         }
7382         fflush(serverMoves);
7383     }
7384
7385     if (forwardMostMove+1 >= MAX_MOVES) {
7386       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7387                         0, 1);
7388       return;
7389     }
7390     SwitchClocks();
7391     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7392     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7393     if (commentList[forwardMostMove+1] != NULL) {
7394         free(commentList[forwardMostMove+1]);
7395         commentList[forwardMostMove+1] = NULL;
7396     }
7397     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7398     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7399     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7400                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7401     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7402     gameInfo.result = GameUnfinished;
7403     if (gameInfo.resultDetails != NULL) {
7404         free(gameInfo.resultDetails);
7405         gameInfo.resultDetails = NULL;
7406     }
7407     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7408                               moveList[forwardMostMove - 1]);
7409     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7410                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7411                              fromY, fromX, toY, toX, promoChar,
7412                              parseList[forwardMostMove - 1]);
7413     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7414                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7415                             castlingRights[forwardMostMove]) ) {
7416       case MT_NONE:
7417       case MT_STALEMATE:
7418       default:
7419         break;
7420       case MT_CHECK:
7421         if(gameInfo.variant != VariantShogi)
7422             strcat(parseList[forwardMostMove - 1], "+");
7423         break;
7424       case MT_CHECKMATE:
7425       case MT_STAINMATE:
7426         strcat(parseList[forwardMostMove - 1], "#");
7427         break;
7428     }
7429     if (appData.debugMode) {
7430         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7431     }
7432
7433 }
7434
7435 /* Updates currentMove if not pausing */
7436 void
7437 ShowMove(fromX, fromY, toX, toY)
7438 {
7439     int instant = (gameMode == PlayFromGameFile) ?
7440         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7441     if(appData.noGUI) return;
7442     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7443         if (!instant) {
7444             if (forwardMostMove == currentMove + 1) {
7445                 AnimateMove(boards[forwardMostMove - 1],
7446                             fromX, fromY, toX, toY);
7447             }
7448             if (appData.highlightLastMove) {
7449                 SetHighlights(fromX, fromY, toX, toY);
7450             }
7451         }
7452         currentMove = forwardMostMove;
7453     }
7454
7455     if (instant) return;
7456
7457     DisplayMove(currentMove - 1);
7458     DrawPosition(FALSE, boards[currentMove]);
7459     DisplayBothClocks();
7460     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7461 }
7462
7463 void SendEgtPath(ChessProgramState *cps)
7464 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7465         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7466
7467         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7468
7469         while(*p) {
7470             char c, *q = name+1, *r, *s;
7471
7472             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7473             while(*p && *p != ',') *q++ = *p++;
7474             *q++ = ':'; *q = 0;
7475             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7476                 strcmp(name, ",nalimov:") == 0 ) {
7477                 // take nalimov path from the menu-changeable option first, if it is defined
7478                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7479                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7480             } else
7481             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7482                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7483                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7484                 s = r = StrStr(s, ":") + 1; // beginning of path info
7485                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7486                 c = *r; *r = 0;             // temporarily null-terminate path info
7487                     *--q = 0;               // strip of trailig ':' from name
7488                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7489                 *r = c;
7490                 SendToProgram(buf,cps);     // send egtbpath command for this format
7491             }
7492             if(*p == ',') p++; // read away comma to position for next format name
7493         }
7494 }
7495
7496 void
7497 InitChessProgram(cps, setup)
7498      ChessProgramState *cps;
7499      int setup; /* [HGM] needed to setup FRC opening position */
7500 {
7501     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7502     if (appData.noChessProgram) return;
7503     hintRequested = FALSE;
7504     bookRequested = FALSE;
7505
7506     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7507     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7508     if(cps->memSize) { /* [HGM] memory */
7509         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7510         SendToProgram(buf, cps);
7511     }
7512     SendEgtPath(cps); /* [HGM] EGT */
7513     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7514         sprintf(buf, "cores %d\n", appData.smpCores);
7515         SendToProgram(buf, cps);
7516     }
7517
7518     SendToProgram(cps->initString, cps);
7519     if (gameInfo.variant != VariantNormal &&
7520         gameInfo.variant != VariantLoadable
7521         /* [HGM] also send variant if board size non-standard */
7522         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7523                                             ) {
7524       char *v = VariantName(gameInfo.variant);
7525       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7526         /* [HGM] in protocol 1 we have to assume all variants valid */
7527         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7528         DisplayFatalError(buf, 0, 1);
7529         return;
7530       }
7531
7532       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7533       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7534       if( gameInfo.variant == VariantXiangqi )
7535            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7536       if( gameInfo.variant == VariantShogi )
7537            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7538       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7539            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7540       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7541                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7542            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7543       if( gameInfo.variant == VariantCourier )
7544            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7545       if( gameInfo.variant == VariantSuper )
7546            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7547       if( gameInfo.variant == VariantGreat )
7548            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7549
7550       if(overruled) {
7551            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7552                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7553            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7554            if(StrStr(cps->variants, b) == NULL) { 
7555                // specific sized variant not known, check if general sizing allowed
7556                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7557                    if(StrStr(cps->variants, "boardsize") == NULL) {
7558                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7559                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7560                        DisplayFatalError(buf, 0, 1);
7561                        return;
7562                    }
7563                    /* [HGM] here we really should compare with the maximum supported board size */
7564                }
7565            }
7566       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7567       sprintf(buf, "variant %s\n", b);
7568       SendToProgram(buf, cps);
7569     }
7570     currentlyInitializedVariant = gameInfo.variant;
7571
7572     /* [HGM] send opening position in FRC to first engine */
7573     if(setup) {
7574           SendToProgram("force\n", cps);
7575           SendBoard(cps, 0);
7576           /* engine is now in force mode! Set flag to wake it up after first move. */
7577           setboardSpoiledMachineBlack = 1;
7578     }
7579
7580     if (cps->sendICS) {
7581       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7582       SendToProgram(buf, cps);
7583     }
7584     cps->maybeThinking = FALSE;
7585     cps->offeredDraw = 0;
7586     if (!appData.icsActive) {
7587         SendTimeControl(cps, movesPerSession, timeControl,
7588                         timeIncrement, appData.searchDepth,
7589                         searchTime);
7590     }
7591     if (appData.showThinking 
7592         // [HGM] thinking: four options require thinking output to be sent
7593         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7594                                 ) {
7595         SendToProgram("post\n", cps);
7596     }
7597     SendToProgram("hard\n", cps);
7598     if (!appData.ponderNextMove) {
7599         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7600            it without being sure what state we are in first.  "hard"
7601            is not a toggle, so that one is OK.
7602          */
7603         SendToProgram("easy\n", cps);
7604     }
7605     if (cps->usePing) {
7606       sprintf(buf, "ping %d\n", ++cps->lastPing);
7607       SendToProgram(buf, cps);
7608     }
7609     cps->initDone = TRUE;
7610 }   
7611
7612
7613 void
7614 StartChessProgram(cps)
7615      ChessProgramState *cps;
7616 {
7617     char buf[MSG_SIZ];
7618     int err;
7619
7620     if (appData.noChessProgram) return;
7621     cps->initDone = FALSE;
7622
7623     if (strcmp(cps->host, "localhost") == 0) {
7624         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7625     } else if (*appData.remoteShell == NULLCHAR) {
7626         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7627     } else {
7628         if (*appData.remoteUser == NULLCHAR) {
7629           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7630                     cps->program);
7631         } else {
7632           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7633                     cps->host, appData.remoteUser, cps->program);
7634         }
7635         err = StartChildProcess(buf, "", &cps->pr);
7636     }
7637     
7638     if (err != 0) {
7639         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7640         DisplayFatalError(buf, err, 1);
7641         cps->pr = NoProc;
7642         cps->isr = NULL;
7643         return;
7644     }
7645     
7646     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7647     if (cps->protocolVersion > 1) {
7648       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7649       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7650       cps->comboCnt = 0;  //                and values of combo boxes
7651       SendToProgram(buf, cps);
7652     } else {
7653       SendToProgram("xboard\n", cps);
7654     }
7655 }
7656
7657
7658 void
7659 TwoMachinesEventIfReady P((void))
7660 {
7661   if (first.lastPing != first.lastPong) {
7662     DisplayMessage("", _("Waiting for first chess program"));
7663     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7664     return;
7665   }
7666   if (second.lastPing != second.lastPong) {
7667     DisplayMessage("", _("Waiting for second chess program"));
7668     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7669     return;
7670   }
7671   ThawUI();
7672   TwoMachinesEvent();
7673 }
7674
7675 void
7676 NextMatchGame P((void))
7677 {
7678     int index; /* [HGM] autoinc: step lod index during match */
7679     Reset(FALSE, TRUE);
7680     if (*appData.loadGameFile != NULLCHAR) {
7681         index = appData.loadGameIndex;
7682         if(index < 0) { // [HGM] autoinc
7683             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7684             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7685         } 
7686         LoadGameFromFile(appData.loadGameFile,
7687                          index,
7688                          appData.loadGameFile, FALSE);
7689     } else if (*appData.loadPositionFile != NULLCHAR) {
7690         index = appData.loadPositionIndex;
7691         if(index < 0) { // [HGM] autoinc
7692             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7693             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7694         } 
7695         LoadPositionFromFile(appData.loadPositionFile,
7696                              index,
7697                              appData.loadPositionFile);
7698     }
7699     TwoMachinesEventIfReady();
7700 }
7701
7702 void UserAdjudicationEvent( int result )
7703 {
7704     ChessMove gameResult = GameIsDrawn;
7705
7706     if( result > 0 ) {
7707         gameResult = WhiteWins;
7708     }
7709     else if( result < 0 ) {
7710         gameResult = BlackWins;
7711     }
7712
7713     if( gameMode == TwoMachinesPlay ) {
7714         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7715     }
7716 }
7717
7718
7719 void
7720 GameEnds(result, resultDetails, whosays)
7721      ChessMove result;
7722      char *resultDetails;
7723      int whosays;
7724 {
7725     GameMode nextGameMode;
7726     int isIcsGame;
7727     char buf[MSG_SIZ];
7728
7729     if(endingGame) return; /* [HGM] crash: forbid recursion */
7730     endingGame = 1;
7731
7732     if (appData.debugMode) {
7733       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7734               result, resultDetails ? resultDetails : "(null)", whosays);
7735     }
7736
7737     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7738         /* If we are playing on ICS, the server decides when the
7739            game is over, but the engine can offer to draw, claim 
7740            a draw, or resign. 
7741          */
7742 #if ZIPPY
7743         if (appData.zippyPlay && first.initDone) {
7744             if (result == GameIsDrawn) {
7745                 /* In case draw still needs to be claimed */
7746                 SendToICS(ics_prefix);
7747                 SendToICS("draw\n");
7748             } else if (StrCaseStr(resultDetails, "resign")) {
7749                 SendToICS(ics_prefix);
7750                 SendToICS("resign\n");
7751             }
7752         }
7753 #endif
7754         endingGame = 0; /* [HGM] crash */
7755         return;
7756     }
7757
7758     /* If we're loading the game from a file, stop */
7759     if (whosays == GE_FILE) {
7760       (void) StopLoadGameTimer();
7761       gameFileFP = NULL;
7762     }
7763
7764     /* Cancel draw offers */
7765     first.offeredDraw = second.offeredDraw = 0;
7766
7767     /* If this is an ICS game, only ICS can really say it's done;
7768        if not, anyone can. */
7769     isIcsGame = (gameMode == IcsPlayingWhite || 
7770                  gameMode == IcsPlayingBlack || 
7771                  gameMode == IcsObserving    || 
7772                  gameMode == IcsExamining);
7773
7774     if (!isIcsGame || whosays == GE_ICS) {
7775         /* OK -- not an ICS game, or ICS said it was done */
7776         StopClocks();
7777         if (!isIcsGame && !appData.noChessProgram) 
7778           SetUserThinkingEnables();
7779     
7780         /* [HGM] if a machine claims the game end we verify this claim */
7781         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7782             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7783                 char claimer;
7784                 ChessMove trueResult = (ChessMove) -1;
7785
7786                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7787                                             first.twoMachinesColor[0] :
7788                                             second.twoMachinesColor[0] ;
7789
7790                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7791                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7792                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7793                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7794                 } else
7795                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7796                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7797                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7798                 } else
7799                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7800                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7801                 }
7802
7803                 // now verify win claims, but not in drop games, as we don't understand those yet
7804                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7805                                                  || gameInfo.variant == VariantGreat) &&
7806                     (result == WhiteWins && claimer == 'w' ||
7807                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7808                       if (appData.debugMode) {
7809                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7810                                 result, epStatus[forwardMostMove], forwardMostMove);
7811                       }
7812                       if(result != trueResult) {
7813                               sprintf(buf, "False win claim: '%s'", resultDetails);
7814                               result = claimer == 'w' ? BlackWins : WhiteWins;
7815                               resultDetails = buf;
7816                       }
7817                 } else
7818                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7819                     && (forwardMostMove <= backwardMostMove ||
7820                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7821                         (claimer=='b')==(forwardMostMove&1))
7822                                                                                   ) {
7823                       /* [HGM] verify: draws that were not flagged are false claims */
7824                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7825                       result = claimer == 'w' ? BlackWins : WhiteWins;
7826                       resultDetails = buf;
7827                 }
7828                 /* (Claiming a loss is accepted no questions asked!) */
7829             }
7830             /* [HGM] bare: don't allow bare King to win */
7831             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7832                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7833                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7834                && result != GameIsDrawn)
7835             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7836                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7837                         int p = (int)boards[forwardMostMove][i][j] - color;
7838                         if(p >= 0 && p <= (int)WhiteKing) k++;
7839                 }
7840                 if (appData.debugMode) {
7841                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7842                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7843                 }
7844                 if(k <= 1) {
7845                         result = GameIsDrawn;
7846                         sprintf(buf, "%s but bare king", resultDetails);
7847                         resultDetails = buf;
7848                 }
7849             }
7850         }
7851
7852
7853         if(serverMoves != NULL && !loadFlag) { char c = '=';
7854             if(result==WhiteWins) c = '+';
7855             if(result==BlackWins) c = '-';
7856             if(resultDetails != NULL)
7857                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7858         }
7859         if (resultDetails != NULL) {
7860             gameInfo.result = result;
7861             gameInfo.resultDetails = StrSave(resultDetails);
7862
7863             /* display last move only if game was not loaded from file */
7864             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7865                 DisplayMove(currentMove - 1);
7866     
7867             if (forwardMostMove != 0) {
7868                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7869                     if (*appData.saveGameFile != NULLCHAR) {
7870                         SaveGameToFile(appData.saveGameFile, TRUE);
7871                     } else if (appData.autoSaveGames) {
7872                         AutoSaveGame();
7873                     }
7874                     if (*appData.savePositionFile != NULLCHAR) {
7875                         SavePositionToFile(appData.savePositionFile);
7876                     }
7877                 }
7878             }
7879
7880             /* Tell program how game ended in case it is learning */
7881             /* [HGM] Moved this to after saving the PGN, just in case */
7882             /* engine died and we got here through time loss. In that */
7883             /* case we will get a fatal error writing the pipe, which */
7884             /* would otherwise lose us the PGN.                       */
7885             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7886             /* output during GameEnds should never be fatal anymore   */
7887             if (gameMode == MachinePlaysWhite ||
7888                 gameMode == MachinePlaysBlack ||
7889                 gameMode == TwoMachinesPlay ||
7890                 gameMode == IcsPlayingWhite ||
7891                 gameMode == IcsPlayingBlack ||
7892                 gameMode == BeginningOfGame) {
7893                 char buf[MSG_SIZ];
7894                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7895                         resultDetails);
7896                 if (first.pr != NoProc) {
7897                     SendToProgram(buf, &first);
7898                 }
7899                 if (second.pr != NoProc &&
7900                     gameMode == TwoMachinesPlay) {
7901                     SendToProgram(buf, &second);
7902                 }
7903             }
7904         }
7905
7906         if (appData.icsActive) {
7907             if (appData.quietPlay &&
7908                 (gameMode == IcsPlayingWhite ||
7909                  gameMode == IcsPlayingBlack)) {
7910                 SendToICS(ics_prefix);
7911                 SendToICS("set shout 1\n");
7912             }
7913             nextGameMode = IcsIdle;
7914             ics_user_moved = FALSE;
7915             /* clean up premove.  It's ugly when the game has ended and the
7916              * premove highlights are still on the board.
7917              */
7918             if (gotPremove) {
7919               gotPremove = FALSE;
7920               ClearPremoveHighlights();
7921               DrawPosition(FALSE, boards[currentMove]);
7922             }
7923             if (whosays == GE_ICS) {
7924                 switch (result) {
7925                 case WhiteWins:
7926                     if (gameMode == IcsPlayingWhite)
7927                         PlayIcsWinSound();
7928                     else if(gameMode == IcsPlayingBlack)
7929                         PlayIcsLossSound();
7930                     break;
7931                 case BlackWins:
7932                     if (gameMode == IcsPlayingBlack)
7933                         PlayIcsWinSound();
7934                     else if(gameMode == IcsPlayingWhite)
7935                         PlayIcsLossSound();
7936                     break;
7937                 case GameIsDrawn:
7938                     PlayIcsDrawSound();
7939                     break;
7940                 default:
7941                     PlayIcsUnfinishedSound();
7942                 }
7943             }
7944         } else if (gameMode == EditGame ||
7945                    gameMode == PlayFromGameFile || 
7946                    gameMode == AnalyzeMode || 
7947                    gameMode == AnalyzeFile) {
7948             nextGameMode = gameMode;
7949         } else {
7950             nextGameMode = EndOfGame;
7951         }
7952         pausing = FALSE;
7953         ModeHighlight();
7954     } else {
7955         nextGameMode = gameMode;
7956     }
7957
7958     if (appData.noChessProgram) {
7959         gameMode = nextGameMode;
7960         ModeHighlight();
7961         endingGame = 0; /* [HGM] crash */
7962         return;
7963     }
7964
7965     if (first.reuse) {
7966         /* Put first chess program into idle state */
7967         if (first.pr != NoProc &&
7968             (gameMode == MachinePlaysWhite ||
7969              gameMode == MachinePlaysBlack ||
7970              gameMode == TwoMachinesPlay ||
7971              gameMode == IcsPlayingWhite ||
7972              gameMode == IcsPlayingBlack ||
7973              gameMode == BeginningOfGame)) {
7974             SendToProgram("force\n", &first);
7975             if (first.usePing) {
7976               char buf[MSG_SIZ];
7977               sprintf(buf, "ping %d\n", ++first.lastPing);
7978               SendToProgram(buf, &first);
7979             }
7980         }
7981     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7982         /* Kill off first chess program */
7983         if (first.isr != NULL)
7984           RemoveInputSource(first.isr);
7985         first.isr = NULL;
7986     
7987         if (first.pr != NoProc) {
7988             ExitAnalyzeMode();
7989             DoSleep( appData.delayBeforeQuit );
7990             SendToProgram("quit\n", &first);
7991             DoSleep( appData.delayAfterQuit );
7992             DestroyChildProcess(first.pr, first.useSigterm);
7993         }
7994         first.pr = NoProc;
7995     }
7996     if (second.reuse) {
7997         /* Put second chess program into idle state */
7998         if (second.pr != NoProc &&
7999             gameMode == TwoMachinesPlay) {
8000             SendToProgram("force\n", &second);
8001             if (second.usePing) {
8002               char buf[MSG_SIZ];
8003               sprintf(buf, "ping %d\n", ++second.lastPing);
8004               SendToProgram(buf, &second);
8005             }
8006         }
8007     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8008         /* Kill off second chess program */
8009         if (second.isr != NULL)
8010           RemoveInputSource(second.isr);
8011         second.isr = NULL;
8012     
8013         if (second.pr != NoProc) {
8014             DoSleep( appData.delayBeforeQuit );
8015             SendToProgram("quit\n", &second);
8016             DoSleep( appData.delayAfterQuit );
8017             DestroyChildProcess(second.pr, second.useSigterm);
8018         }
8019         second.pr = NoProc;
8020     }
8021
8022     if (matchMode && gameMode == TwoMachinesPlay) {
8023         switch (result) {
8024         case WhiteWins:
8025           if (first.twoMachinesColor[0] == 'w') {
8026             first.matchWins++;
8027           } else {
8028             second.matchWins++;
8029           }
8030           break;
8031         case BlackWins:
8032           if (first.twoMachinesColor[0] == 'b') {
8033             first.matchWins++;
8034           } else {
8035             second.matchWins++;
8036           }
8037           break;
8038         default:
8039           break;
8040         }
8041         if (matchGame < appData.matchGames) {
8042             char *tmp;
8043             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8044                 tmp = first.twoMachinesColor;
8045                 first.twoMachinesColor = second.twoMachinesColor;
8046                 second.twoMachinesColor = tmp;
8047             }
8048             gameMode = nextGameMode;
8049             matchGame++;
8050             if(appData.matchPause>10000 || appData.matchPause<10)
8051                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8052             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8053             endingGame = 0; /* [HGM] crash */
8054             return;
8055         } else {
8056             char buf[MSG_SIZ];
8057             gameMode = nextGameMode;
8058             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8059                     first.tidy, second.tidy,
8060                     first.matchWins, second.matchWins,
8061                     appData.matchGames - (first.matchWins + second.matchWins));
8062             DisplayFatalError(buf, 0, 0);
8063         }
8064     }
8065     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8066         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8067       ExitAnalyzeMode();
8068     gameMode = nextGameMode;
8069     ModeHighlight();
8070     endingGame = 0;  /* [HGM] crash */
8071 }
8072
8073 /* Assumes program was just initialized (initString sent).
8074    Leaves program in force mode. */
8075 void
8076 FeedMovesToProgram(cps, upto) 
8077      ChessProgramState *cps;
8078      int upto;
8079 {
8080     int i;
8081     
8082     if (appData.debugMode)
8083       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8084               startedFromSetupPosition ? "position and " : "",
8085               backwardMostMove, upto, cps->which);
8086     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8087         // [HGM] variantswitch: make engine aware of new variant
8088         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8089                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8090         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8091         SendToProgram(buf, cps);
8092         currentlyInitializedVariant = gameInfo.variant;
8093     }
8094     SendToProgram("force\n", cps);
8095     if (startedFromSetupPosition) {
8096         SendBoard(cps, backwardMostMove);
8097     if (appData.debugMode) {
8098         fprintf(debugFP, "feedMoves\n");
8099     }
8100     }
8101     for (i = backwardMostMove; i < upto; i++) {
8102         SendMoveToProgram(i, cps);
8103     }
8104 }
8105
8106
8107 void
8108 ResurrectChessProgram()
8109 {
8110      /* The chess program may have exited.
8111         If so, restart it and feed it all the moves made so far. */
8112
8113     if (appData.noChessProgram || first.pr != NoProc) return;
8114     
8115     StartChessProgram(&first);
8116     InitChessProgram(&first, FALSE);
8117     FeedMovesToProgram(&first, currentMove);
8118
8119     if (!first.sendTime) {
8120         /* can't tell gnuchess what its clock should read,
8121            so we bow to its notion. */
8122         ResetClocks();
8123         timeRemaining[0][currentMove] = whiteTimeRemaining;
8124         timeRemaining[1][currentMove] = blackTimeRemaining;
8125     }
8126
8127     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8128                 appData.icsEngineAnalyze) && first.analysisSupport) {
8129       SendToProgram("analyze\n", &first);
8130       first.analyzing = TRUE;
8131     }
8132 }
8133
8134 /*
8135  * Button procedures
8136  */
8137 void
8138 Reset(redraw, init)
8139      int redraw, init;
8140 {
8141     int i;
8142
8143     if (appData.debugMode) {
8144         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8145                 redraw, init, gameMode);
8146     }
8147     pausing = pauseExamInvalid = FALSE;
8148     startedFromSetupPosition = blackPlaysFirst = FALSE;
8149     firstMove = TRUE;
8150     whiteFlag = blackFlag = FALSE;
8151     userOfferedDraw = FALSE;
8152     hintRequested = bookRequested = FALSE;
8153     first.maybeThinking = FALSE;
8154     second.maybeThinking = FALSE;
8155     first.bookSuspend = FALSE; // [HGM] book
8156     second.bookSuspend = FALSE;
8157     thinkOutput[0] = NULLCHAR;
8158     lastHint[0] = NULLCHAR;
8159     ClearGameInfo(&gameInfo);
8160     gameInfo.variant = StringToVariant(appData.variant);
8161     ics_user_moved = ics_clock_paused = FALSE;
8162     ics_getting_history = H_FALSE;
8163     ics_gamenum = -1;
8164     white_holding[0] = black_holding[0] = NULLCHAR;
8165     ClearProgramStats();
8166     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8167     
8168     ResetFrontEnd();
8169     ClearHighlights();
8170     flipView = appData.flipView;
8171     ClearPremoveHighlights();
8172     gotPremove = FALSE;
8173     alarmSounded = FALSE;
8174
8175     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8176     if(appData.serverMovesName != NULL) {
8177         /* [HGM] prepare to make moves file for broadcasting */
8178         clock_t t = clock();
8179         if(serverMoves != NULL) fclose(serverMoves);
8180         serverMoves = fopen(appData.serverMovesName, "r");
8181         if(serverMoves != NULL) {
8182             fclose(serverMoves);
8183             /* delay 15 sec before overwriting, so all clients can see end */
8184             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8185         }
8186         serverMoves = fopen(appData.serverMovesName, "w");
8187     }
8188
8189     ExitAnalyzeMode();
8190     gameMode = BeginningOfGame;
8191     ModeHighlight();
8192     if(appData.icsActive) gameInfo.variant = VariantNormal;
8193     InitPosition(redraw);
8194     for (i = 0; i < MAX_MOVES; i++) {
8195         if (commentList[i] != NULL) {
8196             free(commentList[i]);
8197             commentList[i] = NULL;
8198         }
8199     }
8200     ResetClocks();
8201     timeRemaining[0][0] = whiteTimeRemaining;
8202     timeRemaining[1][0] = blackTimeRemaining;
8203     if (first.pr == NULL) {
8204         StartChessProgram(&first);
8205     }
8206     if (init) {
8207             InitChessProgram(&first, startedFromSetupPosition);
8208     }
8209     DisplayTitle("");
8210     DisplayMessage("", "");
8211     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8212 }
8213
8214 void
8215 AutoPlayGameLoop()
8216 {
8217     for (;;) {
8218         if (!AutoPlayOneMove())
8219           return;
8220         if (matchMode || appData.timeDelay == 0)
8221           continue;
8222         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8223           return;
8224         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8225         break;
8226     }
8227 }
8228
8229
8230 int
8231 AutoPlayOneMove()
8232 {
8233     int fromX, fromY, toX, toY;
8234
8235     if (appData.debugMode) {
8236       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8237     }
8238
8239     if (gameMode != PlayFromGameFile)
8240       return FALSE;
8241
8242     if (currentMove >= forwardMostMove) {
8243       gameMode = EditGame;
8244       ModeHighlight();
8245
8246       /* [AS] Clear current move marker at the end of a game */
8247       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8248
8249       return FALSE;
8250     }
8251     
8252     toX = moveList[currentMove][2] - AAA;
8253     toY = moveList[currentMove][3] - ONE;
8254
8255     if (moveList[currentMove][1] == '@') {
8256         if (appData.highlightLastMove) {
8257             SetHighlights(-1, -1, toX, toY);
8258         }
8259     } else {
8260         fromX = moveList[currentMove][0] - AAA;
8261         fromY = moveList[currentMove][1] - ONE;
8262
8263         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8264
8265         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8266
8267         if (appData.highlightLastMove) {
8268             SetHighlights(fromX, fromY, toX, toY);
8269         }
8270     }
8271     DisplayMove(currentMove);
8272     SendMoveToProgram(currentMove++, &first);
8273     DisplayBothClocks();
8274     DrawPosition(FALSE, boards[currentMove]);
8275     // [HGM] PV info: always display, routine tests if empty
8276     DisplayComment(currentMove - 1, commentList[currentMove]);
8277     return TRUE;
8278 }
8279
8280
8281 int
8282 LoadGameOneMove(readAhead)
8283      ChessMove readAhead;
8284 {
8285     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8286     char promoChar = NULLCHAR;
8287     ChessMove moveType;
8288     char move[MSG_SIZ];
8289     char *p, *q;
8290     
8291     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8292         gameMode != AnalyzeMode && gameMode != Training) {
8293         gameFileFP = NULL;
8294         return FALSE;
8295     }
8296     
8297     yyboardindex = forwardMostMove;
8298     if (readAhead != (ChessMove)0) {
8299       moveType = readAhead;
8300     } else {
8301       if (gameFileFP == NULL)
8302           return FALSE;
8303       moveType = (ChessMove) yylex();
8304     }
8305     
8306     done = FALSE;
8307     switch (moveType) {
8308       case Comment:
8309         if (appData.debugMode) 
8310           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8311         p = yy_text;
8312         if (*p == '{' || *p == '[' || *p == '(') {
8313             p[strlen(p) - 1] = NULLCHAR;
8314             p++;
8315         }
8316
8317         /* append the comment but don't display it */
8318         while (*p == '\n') p++;
8319         AppendComment(currentMove, p);
8320         return TRUE;
8321
8322       case WhiteCapturesEnPassant:
8323       case BlackCapturesEnPassant:
8324       case WhitePromotionChancellor:
8325       case BlackPromotionChancellor:
8326       case WhitePromotionArchbishop:
8327       case BlackPromotionArchbishop:
8328       case WhitePromotionCentaur:
8329       case BlackPromotionCentaur:
8330       case WhitePromotionQueen:
8331       case BlackPromotionQueen:
8332       case WhitePromotionRook:
8333       case BlackPromotionRook:
8334       case WhitePromotionBishop:
8335       case BlackPromotionBishop:
8336       case WhitePromotionKnight:
8337       case BlackPromotionKnight:
8338       case WhitePromotionKing:
8339       case BlackPromotionKing:
8340       case NormalMove:
8341       case WhiteKingSideCastle:
8342       case WhiteQueenSideCastle:
8343       case BlackKingSideCastle:
8344       case BlackQueenSideCastle:
8345       case WhiteKingSideCastleWild:
8346       case WhiteQueenSideCastleWild:
8347       case BlackKingSideCastleWild:
8348       case BlackQueenSideCastleWild:
8349       /* PUSH Fabien */
8350       case WhiteHSideCastleFR:
8351       case WhiteASideCastleFR:
8352       case BlackHSideCastleFR:
8353       case BlackASideCastleFR:
8354       /* POP Fabien */
8355         if (appData.debugMode)
8356           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8357         fromX = currentMoveString[0] - AAA;
8358         fromY = currentMoveString[1] - ONE;
8359         toX = currentMoveString[2] - AAA;
8360         toY = currentMoveString[3] - ONE;
8361         promoChar = currentMoveString[4];
8362         break;
8363
8364       case WhiteDrop:
8365       case BlackDrop:
8366         if (appData.debugMode)
8367           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8368         fromX = moveType == WhiteDrop ?
8369           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8370         (int) CharToPiece(ToLower(currentMoveString[0]));
8371         fromY = DROP_RANK;
8372         toX = currentMoveString[2] - AAA;
8373         toY = currentMoveString[3] - ONE;
8374         break;
8375
8376       case WhiteWins:
8377       case BlackWins:
8378       case GameIsDrawn:
8379       case GameUnfinished:
8380         if (appData.debugMode)
8381           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8382         p = strchr(yy_text, '{');
8383         if (p == NULL) p = strchr(yy_text, '(');
8384         if (p == NULL) {
8385             p = yy_text;
8386             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8387         } else {
8388             q = strchr(p, *p == '{' ? '}' : ')');
8389             if (q != NULL) *q = NULLCHAR;
8390             p++;
8391         }
8392         GameEnds(moveType, p, GE_FILE);
8393         done = TRUE;
8394         if (cmailMsgLoaded) {
8395             ClearHighlights();
8396             flipView = WhiteOnMove(currentMove);
8397             if (moveType == GameUnfinished) flipView = !flipView;
8398             if (appData.debugMode)
8399               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8400         }
8401         break;
8402
8403       case (ChessMove) 0:       /* end of file */
8404         if (appData.debugMode)
8405           fprintf(debugFP, "Parser hit end of file\n");
8406         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8407                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8408           case MT_NONE:
8409           case MT_CHECK:
8410             break;
8411           case MT_CHECKMATE:
8412           case MT_STAINMATE:
8413             if (WhiteOnMove(currentMove)) {
8414                 GameEnds(BlackWins, "Black mates", GE_FILE);
8415             } else {
8416                 GameEnds(WhiteWins, "White mates", GE_FILE);
8417             }
8418             break;
8419           case MT_STALEMATE:
8420             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8421             break;
8422         }
8423         done = TRUE;
8424         break;
8425
8426       case MoveNumberOne:
8427         if (lastLoadGameStart == GNUChessGame) {
8428             /* GNUChessGames have numbers, but they aren't move numbers */
8429             if (appData.debugMode)
8430               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8431                       yy_text, (int) moveType);
8432             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8433         }
8434         /* else fall thru */
8435
8436       case XBoardGame:
8437       case GNUChessGame:
8438       case PGNTag:
8439         /* Reached start of next game in file */
8440         if (appData.debugMode)
8441           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8442         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8443                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8444           case MT_NONE:
8445           case MT_CHECK:
8446             break;
8447           case MT_CHECKMATE:
8448           case MT_STAINMATE:
8449             if (WhiteOnMove(currentMove)) {
8450                 GameEnds(BlackWins, "Black mates", GE_FILE);
8451             } else {
8452                 GameEnds(WhiteWins, "White mates", GE_FILE);
8453             }
8454             break;
8455           case MT_STALEMATE:
8456             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8457             break;
8458         }
8459         done = TRUE;
8460         break;
8461
8462       case PositionDiagram:     /* should not happen; ignore */
8463       case ElapsedTime:         /* ignore */
8464       case NAG:                 /* ignore */
8465         if (appData.debugMode)
8466           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8467                   yy_text, (int) moveType);
8468         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8469
8470       case IllegalMove:
8471         if (appData.testLegality) {
8472             if (appData.debugMode)
8473               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8474             sprintf(move, _("Illegal move: %d.%s%s"),
8475                     (forwardMostMove / 2) + 1,
8476                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8477             DisplayError(move, 0);
8478             done = TRUE;
8479         } else {
8480             if (appData.debugMode)
8481               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8482                       yy_text, currentMoveString);
8483             fromX = currentMoveString[0] - AAA;
8484             fromY = currentMoveString[1] - ONE;
8485             toX = currentMoveString[2] - AAA;
8486             toY = currentMoveString[3] - ONE;
8487             promoChar = currentMoveString[4];
8488         }
8489         break;
8490
8491       case AmbiguousMove:
8492         if (appData.debugMode)
8493           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8494         sprintf(move, _("Ambiguous move: %d.%s%s"),
8495                 (forwardMostMove / 2) + 1,
8496                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8497         DisplayError(move, 0);
8498         done = TRUE;
8499         break;
8500
8501       default:
8502       case ImpossibleMove:
8503         if (appData.debugMode)
8504           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8505         sprintf(move, _("Illegal move: %d.%s%s"),
8506                 (forwardMostMove / 2) + 1,
8507                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8508         DisplayError(move, 0);
8509         done = TRUE;
8510         break;
8511     }
8512
8513     if (done) {
8514         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8515             DrawPosition(FALSE, boards[currentMove]);
8516             DisplayBothClocks();
8517             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8518               DisplayComment(currentMove - 1, commentList[currentMove]);
8519         }
8520         (void) StopLoadGameTimer();
8521         gameFileFP = NULL;
8522         cmailOldMove = forwardMostMove;
8523         return FALSE;
8524     } else {
8525         /* currentMoveString is set as a side-effect of yylex */
8526         strcat(currentMoveString, "\n");
8527         strcpy(moveList[forwardMostMove], currentMoveString);
8528         
8529         thinkOutput[0] = NULLCHAR;
8530         MakeMove(fromX, fromY, toX, toY, promoChar);
8531         currentMove = forwardMostMove;
8532         return TRUE;
8533     }
8534 }
8535
8536 /* Load the nth game from the given file */
8537 int
8538 LoadGameFromFile(filename, n, title, useList)
8539      char *filename;
8540      int n;
8541      char *title;
8542      /*Boolean*/ int useList;
8543 {
8544     FILE *f;
8545     char buf[MSG_SIZ];
8546
8547     if (strcmp(filename, "-") == 0) {
8548         f = stdin;
8549         title = "stdin";
8550     } else {
8551         f = fopen(filename, "rb");
8552         if (f == NULL) {
8553           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8554             DisplayError(buf, errno);
8555             return FALSE;
8556         }
8557     }
8558     if (fseek(f, 0, 0) == -1) {
8559         /* f is not seekable; probably a pipe */
8560         useList = FALSE;
8561     }
8562     if (useList && n == 0) {
8563         int error = GameListBuild(f);
8564         if (error) {
8565             DisplayError(_("Cannot build game list"), error);
8566         } else if (!ListEmpty(&gameList) &&
8567                    ((ListGame *) gameList.tailPred)->number > 1) {
8568             GameListPopUp(f, title);
8569             return TRUE;
8570         }
8571         GameListDestroy();
8572         n = 1;
8573     }
8574     if (n == 0) n = 1;
8575     return LoadGame(f, n, title, FALSE);
8576 }
8577
8578
8579 void
8580 MakeRegisteredMove()
8581 {
8582     int fromX, fromY, toX, toY;
8583     char promoChar;
8584     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8585         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8586           case CMAIL_MOVE:
8587           case CMAIL_DRAW:
8588             if (appData.debugMode)
8589               fprintf(debugFP, "Restoring %s for game %d\n",
8590                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8591     
8592             thinkOutput[0] = NULLCHAR;
8593             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8594             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8595             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8596             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8597             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8598             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8599             MakeMove(fromX, fromY, toX, toY, promoChar);
8600             ShowMove(fromX, fromY, toX, toY);
8601               
8602             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8603                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8604               case MT_NONE:
8605               case MT_CHECK:
8606                 break;
8607                 
8608               case MT_CHECKMATE:
8609               case MT_STAINMATE:
8610                 if (WhiteOnMove(currentMove)) {
8611                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8612                 } else {
8613                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8614                 }
8615                 break;
8616                 
8617               case MT_STALEMATE:
8618                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8619                 break;
8620             }
8621
8622             break;
8623             
8624           case CMAIL_RESIGN:
8625             if (WhiteOnMove(currentMove)) {
8626                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8627             } else {
8628                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8629             }
8630             break;
8631             
8632           case CMAIL_ACCEPT:
8633             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8634             break;
8635               
8636           default:
8637             break;
8638         }
8639     }
8640
8641     return;
8642 }
8643
8644 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8645 int
8646 CmailLoadGame(f, gameNumber, title, useList)
8647      FILE *f;
8648      int gameNumber;
8649      char *title;
8650      int useList;
8651 {
8652     int retVal;
8653
8654     if (gameNumber > nCmailGames) {
8655         DisplayError(_("No more games in this message"), 0);
8656         return FALSE;
8657     }
8658     if (f == lastLoadGameFP) {
8659         int offset = gameNumber - lastLoadGameNumber;
8660         if (offset == 0) {
8661             cmailMsg[0] = NULLCHAR;
8662             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8663                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8664                 nCmailMovesRegistered--;
8665             }
8666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8667             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8668                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8669             }
8670         } else {
8671             if (! RegisterMove()) return FALSE;
8672         }
8673     }
8674
8675     retVal = LoadGame(f, gameNumber, title, useList);
8676
8677     /* Make move registered during previous look at this game, if any */
8678     MakeRegisteredMove();
8679
8680     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8681         commentList[currentMove]
8682           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8683         DisplayComment(currentMove - 1, commentList[currentMove]);
8684     }
8685
8686     return retVal;
8687 }
8688
8689 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8690 int
8691 ReloadGame(offset)
8692      int offset;
8693 {
8694     int gameNumber = lastLoadGameNumber + offset;
8695     if (lastLoadGameFP == NULL) {
8696         DisplayError(_("No game has been loaded yet"), 0);
8697         return FALSE;
8698     }
8699     if (gameNumber <= 0) {
8700         DisplayError(_("Can't back up any further"), 0);
8701         return FALSE;
8702     }
8703     if (cmailMsgLoaded) {
8704         return CmailLoadGame(lastLoadGameFP, gameNumber,
8705                              lastLoadGameTitle, lastLoadGameUseList);
8706     } else {
8707         return LoadGame(lastLoadGameFP, gameNumber,
8708                         lastLoadGameTitle, lastLoadGameUseList);
8709     }
8710 }
8711
8712
8713
8714 /* Load the nth game from open file f */
8715 int
8716 LoadGame(f, gameNumber, title, useList)
8717      FILE *f;
8718      int gameNumber;
8719      char *title;
8720      int useList;
8721 {
8722     ChessMove cm;
8723     char buf[MSG_SIZ];
8724     int gn = gameNumber;
8725     ListGame *lg = NULL;
8726     int numPGNTags = 0;
8727     int err;
8728     GameMode oldGameMode;
8729     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8730
8731     if (appData.debugMode) 
8732         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8733
8734     if (gameMode == Training )
8735         SetTrainingModeOff();
8736
8737     oldGameMode = gameMode;
8738     if (gameMode != BeginningOfGame) {
8739       Reset(FALSE, TRUE);
8740     }
8741
8742     gameFileFP = f;
8743     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8744         fclose(lastLoadGameFP);
8745     }
8746
8747     if (useList) {
8748         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8749         
8750         if (lg) {
8751             fseek(f, lg->offset, 0);
8752             GameListHighlight(gameNumber);
8753             gn = 1;
8754         }
8755         else {
8756             DisplayError(_("Game number out of range"), 0);
8757             return FALSE;
8758         }
8759     } else {
8760         GameListDestroy();
8761         if (fseek(f, 0, 0) == -1) {
8762             if (f == lastLoadGameFP ?
8763                 gameNumber == lastLoadGameNumber + 1 :
8764                 gameNumber == 1) {
8765                 gn = 1;
8766             } else {
8767                 DisplayError(_("Can't seek on game file"), 0);
8768                 return FALSE;
8769             }
8770         }
8771     }
8772     lastLoadGameFP = f;
8773     lastLoadGameNumber = gameNumber;
8774     strcpy(lastLoadGameTitle, title);
8775     lastLoadGameUseList = useList;
8776
8777     yynewfile(f);
8778
8779     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8780       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8781                 lg->gameInfo.black);
8782             DisplayTitle(buf);
8783     } else if (*title != NULLCHAR) {
8784         if (gameNumber > 1) {
8785             sprintf(buf, "%s %d", title, gameNumber);
8786             DisplayTitle(buf);
8787         } else {
8788             DisplayTitle(title);
8789         }
8790     }
8791
8792     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8793         gameMode = PlayFromGameFile;
8794         ModeHighlight();
8795     }
8796
8797     currentMove = forwardMostMove = backwardMostMove = 0;
8798     CopyBoard(boards[0], initialPosition);
8799     StopClocks();
8800
8801     /*
8802      * Skip the first gn-1 games in the file.
8803      * Also skip over anything that precedes an identifiable 
8804      * start of game marker, to avoid being confused by 
8805      * garbage at the start of the file.  Currently 
8806      * recognized start of game markers are the move number "1",
8807      * the pattern "gnuchess .* game", the pattern
8808      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8809      * A game that starts with one of the latter two patterns
8810      * will also have a move number 1, possibly
8811      * following a position diagram.
8812      * 5-4-02: Let's try being more lenient and allowing a game to
8813      * start with an unnumbered move.  Does that break anything?
8814      */
8815     cm = lastLoadGameStart = (ChessMove) 0;
8816     while (gn > 0) {
8817         yyboardindex = forwardMostMove;
8818         cm = (ChessMove) yylex();
8819         switch (cm) {
8820           case (ChessMove) 0:
8821             if (cmailMsgLoaded) {
8822                 nCmailGames = CMAIL_MAX_GAMES - gn;
8823             } else {
8824                 Reset(TRUE, TRUE);
8825                 DisplayError(_("Game not found in file"), 0);
8826             }
8827             return FALSE;
8828
8829           case GNUChessGame:
8830           case XBoardGame:
8831             gn--;
8832             lastLoadGameStart = cm;
8833             break;
8834             
8835           case MoveNumberOne:
8836             switch (lastLoadGameStart) {
8837               case GNUChessGame:
8838               case XBoardGame:
8839               case PGNTag:
8840                 break;
8841               case MoveNumberOne:
8842               case (ChessMove) 0:
8843                 gn--;           /* count this game */
8844                 lastLoadGameStart = cm;
8845                 break;
8846               default:
8847                 /* impossible */
8848                 break;
8849             }
8850             break;
8851
8852           case PGNTag:
8853             switch (lastLoadGameStart) {
8854               case GNUChessGame:
8855               case PGNTag:
8856               case MoveNumberOne:
8857               case (ChessMove) 0:
8858                 gn--;           /* count this game */
8859                 lastLoadGameStart = cm;
8860                 break;
8861               case XBoardGame:
8862                 lastLoadGameStart = cm; /* game counted already */
8863                 break;
8864               default:
8865                 /* impossible */
8866                 break;
8867             }
8868             if (gn > 0) {
8869                 do {
8870                     yyboardindex = forwardMostMove;
8871                     cm = (ChessMove) yylex();
8872                 } while (cm == PGNTag || cm == Comment);
8873             }
8874             break;
8875
8876           case WhiteWins:
8877           case BlackWins:
8878           case GameIsDrawn:
8879             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8880                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8881                     != CMAIL_OLD_RESULT) {
8882                     nCmailResults ++ ;
8883                     cmailResult[  CMAIL_MAX_GAMES
8884                                 - gn - 1] = CMAIL_OLD_RESULT;
8885                 }
8886             }
8887             break;
8888
8889           case NormalMove:
8890             /* Only a NormalMove can be at the start of a game
8891              * without a position diagram. */
8892             if (lastLoadGameStart == (ChessMove) 0) {
8893               gn--;
8894               lastLoadGameStart = MoveNumberOne;
8895             }
8896             break;
8897
8898           default:
8899             break;
8900         }
8901     }
8902     
8903     if (appData.debugMode)
8904       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8905
8906     if (cm == XBoardGame) {
8907         /* Skip any header junk before position diagram and/or move 1 */
8908         for (;;) {
8909             yyboardindex = forwardMostMove;
8910             cm = (ChessMove) yylex();
8911
8912             if (cm == (ChessMove) 0 ||
8913                 cm == GNUChessGame || cm == XBoardGame) {
8914                 /* Empty game; pretend end-of-file and handle later */
8915                 cm = (ChessMove) 0;
8916                 break;
8917             }
8918
8919             if (cm == MoveNumberOne || cm == PositionDiagram ||
8920                 cm == PGNTag || cm == Comment)
8921               break;
8922         }
8923     } else if (cm == GNUChessGame) {
8924         if (gameInfo.event != NULL) {
8925             free(gameInfo.event);
8926         }
8927         gameInfo.event = StrSave(yy_text);
8928     }   
8929
8930     startedFromSetupPosition = FALSE;
8931     while (cm == PGNTag) {
8932         if (appData.debugMode) 
8933           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8934         err = ParsePGNTag(yy_text, &gameInfo);
8935         if (!err) numPGNTags++;
8936
8937         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8938         if(gameInfo.variant != oldVariant) {
8939             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8940             InitPosition(TRUE);
8941             oldVariant = gameInfo.variant;
8942             if (appData.debugMode) 
8943               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8944         }
8945
8946
8947         if (gameInfo.fen != NULL) {
8948           Board initial_position;
8949           startedFromSetupPosition = TRUE;
8950           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8951             Reset(TRUE, TRUE);
8952             DisplayError(_("Bad FEN position in file"), 0);
8953             return FALSE;
8954           }
8955           CopyBoard(boards[0], initial_position);
8956           if (blackPlaysFirst) {
8957             currentMove = forwardMostMove = backwardMostMove = 1;
8958             CopyBoard(boards[1], initial_position);
8959             strcpy(moveList[0], "");
8960             strcpy(parseList[0], "");
8961             timeRemaining[0][1] = whiteTimeRemaining;
8962             timeRemaining[1][1] = blackTimeRemaining;
8963             if (commentList[0] != NULL) {
8964               commentList[1] = commentList[0];
8965               commentList[0] = NULL;
8966             }
8967           } else {
8968             currentMove = forwardMostMove = backwardMostMove = 0;
8969           }
8970           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8971           {   int i;
8972               initialRulePlies = FENrulePlies;
8973               epStatus[forwardMostMove] = FENepStatus;
8974               for( i=0; i< nrCastlingRights; i++ )
8975                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8976           }
8977           yyboardindex = forwardMostMove;
8978           free(gameInfo.fen);
8979           gameInfo.fen = NULL;
8980         }
8981
8982         yyboardindex = forwardMostMove;
8983         cm = (ChessMove) yylex();
8984
8985         /* Handle comments interspersed among the tags */
8986         while (cm == Comment) {
8987             char *p;
8988             if (appData.debugMode) 
8989               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8990             p = yy_text;
8991             if (*p == '{' || *p == '[' || *p == '(') {
8992                 p[strlen(p) - 1] = NULLCHAR;
8993                 p++;
8994             }
8995             while (*p == '\n') p++;
8996             AppendComment(currentMove, p);
8997             yyboardindex = forwardMostMove;
8998             cm = (ChessMove) yylex();
8999         }
9000     }
9001
9002     /* don't rely on existence of Event tag since if game was
9003      * pasted from clipboard the Event tag may not exist
9004      */
9005     if (numPGNTags > 0){
9006         char *tags;
9007         if (gameInfo.variant == VariantNormal) {
9008           gameInfo.variant = StringToVariant(gameInfo.event);
9009         }
9010         if (!matchMode) {
9011           if( appData.autoDisplayTags ) {
9012             tags = PGNTags(&gameInfo);
9013             TagsPopUp(tags, CmailMsg());
9014             free(tags);
9015           }
9016         }
9017     } else {
9018         /* Make something up, but don't display it now */
9019         SetGameInfo();
9020         TagsPopDown();
9021     }
9022
9023     if (cm == PositionDiagram) {
9024         int i, j;
9025         char *p;
9026         Board initial_position;
9027
9028         if (appData.debugMode)
9029           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9030
9031         if (!startedFromSetupPosition) {
9032             p = yy_text;
9033             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9034               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9035                 switch (*p) {
9036                   case '[':
9037                   case '-':
9038                   case ' ':
9039                   case '\t':
9040                   case '\n':
9041                   case '\r':
9042                     break;
9043                   default:
9044                     initial_position[i][j++] = CharToPiece(*p);
9045                     break;
9046                 }
9047             while (*p == ' ' || *p == '\t' ||
9048                    *p == '\n' || *p == '\r') p++;
9049         
9050             if (strncmp(p, "black", strlen("black"))==0)
9051               blackPlaysFirst = TRUE;
9052             else
9053               blackPlaysFirst = FALSE;
9054             startedFromSetupPosition = TRUE;
9055         
9056             CopyBoard(boards[0], initial_position);
9057             if (blackPlaysFirst) {
9058                 currentMove = forwardMostMove = backwardMostMove = 1;
9059                 CopyBoard(boards[1], initial_position);
9060                 strcpy(moveList[0], "");
9061                 strcpy(parseList[0], "");
9062                 timeRemaining[0][1] = whiteTimeRemaining;
9063                 timeRemaining[1][1] = blackTimeRemaining;
9064                 if (commentList[0] != NULL) {
9065                     commentList[1] = commentList[0];
9066                     commentList[0] = NULL;
9067                 }
9068             } else {
9069                 currentMove = forwardMostMove = backwardMostMove = 0;
9070             }
9071         }
9072         yyboardindex = forwardMostMove;
9073         cm = (ChessMove) yylex();
9074     }
9075
9076     if (first.pr == NoProc) {
9077         StartChessProgram(&first);
9078     }
9079     InitChessProgram(&first, FALSE);
9080     SendToProgram("force\n", &first);
9081     if (startedFromSetupPosition) {
9082         SendBoard(&first, forwardMostMove);
9083     if (appData.debugMode) {
9084         fprintf(debugFP, "Load Game\n");
9085     }
9086         DisplayBothClocks();
9087     }      
9088
9089     /* [HGM] server: flag to write setup moves in broadcast file as one */
9090     loadFlag = appData.suppressLoadMoves;
9091
9092     while (cm == Comment) {
9093         char *p;
9094         if (appData.debugMode) 
9095           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9096         p = yy_text;
9097         if (*p == '{' || *p == '[' || *p == '(') {
9098             p[strlen(p) - 1] = NULLCHAR;
9099             p++;
9100         }
9101         while (*p == '\n') p++;
9102         AppendComment(currentMove, p);
9103         yyboardindex = forwardMostMove;
9104         cm = (ChessMove) yylex();
9105     }
9106
9107     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9108         cm == WhiteWins || cm == BlackWins ||
9109         cm == GameIsDrawn || cm == GameUnfinished) {
9110         DisplayMessage("", _("No moves in game"));
9111         if (cmailMsgLoaded) {
9112             if (appData.debugMode)
9113               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9114             ClearHighlights();
9115             flipView = FALSE;
9116         }
9117         DrawPosition(FALSE, boards[currentMove]);
9118         DisplayBothClocks();
9119         gameMode = EditGame;
9120         ModeHighlight();
9121         gameFileFP = NULL;
9122         cmailOldMove = 0;
9123         return TRUE;
9124     }
9125
9126     // [HGM] PV info: routine tests if comment empty
9127     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9128         DisplayComment(currentMove - 1, commentList[currentMove]);
9129     }
9130     if (!matchMode && appData.timeDelay != 0) 
9131       DrawPosition(FALSE, boards[currentMove]);
9132
9133     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9134       programStats.ok_to_send = 1;
9135     }
9136
9137     /* if the first token after the PGN tags is a move
9138      * and not move number 1, retrieve it from the parser 
9139      */
9140     if (cm != MoveNumberOne)
9141         LoadGameOneMove(cm);
9142
9143     /* load the remaining moves from the file */
9144     while (LoadGameOneMove((ChessMove)0)) {
9145       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9146       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9147     }
9148
9149     /* rewind to the start of the game */
9150     currentMove = backwardMostMove;
9151
9152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9153
9154     if (oldGameMode == AnalyzeFile ||
9155         oldGameMode == AnalyzeMode) {
9156       AnalyzeFileEvent();
9157     }
9158
9159     if (matchMode || appData.timeDelay == 0) {
9160       ToEndEvent();
9161       gameMode = EditGame;
9162       ModeHighlight();
9163     } else if (appData.timeDelay > 0) {
9164       AutoPlayGameLoop();
9165     }
9166
9167     if (appData.debugMode) 
9168         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9169
9170     loadFlag = 0; /* [HGM] true game starts */
9171     return TRUE;
9172 }
9173
9174 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9175 int
9176 ReloadPosition(offset)
9177      int offset;
9178 {
9179     int positionNumber = lastLoadPositionNumber + offset;
9180     if (lastLoadPositionFP == NULL) {
9181         DisplayError(_("No position has been loaded yet"), 0);
9182         return FALSE;
9183     }
9184     if (positionNumber <= 0) {
9185         DisplayError(_("Can't back up any further"), 0);
9186         return FALSE;
9187     }
9188     return LoadPosition(lastLoadPositionFP, positionNumber,
9189                         lastLoadPositionTitle);
9190 }
9191
9192 /* Load the nth position from the given file */
9193 int
9194 LoadPositionFromFile(filename, n, title)
9195      char *filename;
9196      int n;
9197      char *title;
9198 {
9199     FILE *f;
9200     char buf[MSG_SIZ];
9201
9202     if (strcmp(filename, "-") == 0) {
9203         return LoadPosition(stdin, n, "stdin");
9204     } else {
9205         f = fopen(filename, "rb");
9206         if (f == NULL) {
9207             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9208             DisplayError(buf, errno);
9209             return FALSE;
9210         } else {
9211             return LoadPosition(f, n, title);
9212         }
9213     }
9214 }
9215
9216 /* Load the nth position from the given open file, and close it */
9217 int
9218 LoadPosition(f, positionNumber, title)
9219      FILE *f;
9220      int positionNumber;
9221      char *title;
9222 {
9223     char *p, line[MSG_SIZ];
9224     Board initial_position;
9225     int i, j, fenMode, pn;
9226     
9227     if (gameMode == Training )
9228         SetTrainingModeOff();
9229
9230     if (gameMode != BeginningOfGame) {
9231         Reset(FALSE, TRUE);
9232     }
9233     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9234         fclose(lastLoadPositionFP);
9235     }
9236     if (positionNumber == 0) positionNumber = 1;
9237     lastLoadPositionFP = f;
9238     lastLoadPositionNumber = positionNumber;
9239     strcpy(lastLoadPositionTitle, title);
9240     if (first.pr == NoProc) {
9241       StartChessProgram(&first);
9242       InitChessProgram(&first, FALSE);
9243     }    
9244     pn = positionNumber;
9245     if (positionNumber < 0) {
9246         /* Negative position number means to seek to that byte offset */
9247         if (fseek(f, -positionNumber, 0) == -1) {
9248             DisplayError(_("Can't seek on position file"), 0);
9249             return FALSE;
9250         };
9251         pn = 1;
9252     } else {
9253         if (fseek(f, 0, 0) == -1) {
9254             if (f == lastLoadPositionFP ?
9255                 positionNumber == lastLoadPositionNumber + 1 :
9256                 positionNumber == 1) {
9257                 pn = 1;
9258             } else {
9259                 DisplayError(_("Can't seek on position file"), 0);
9260                 return FALSE;
9261             }
9262         }
9263     }
9264     /* See if this file is FEN or old-style xboard */
9265     if (fgets(line, MSG_SIZ, f) == NULL) {
9266         DisplayError(_("Position not found in file"), 0);
9267         return FALSE;
9268     }
9269 #if 0
9270     switch (line[0]) {
9271       case '#':  case 'x':
9272       default:
9273         fenMode = FALSE;
9274         break;
9275       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9276       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9277       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9278       case '7':  case '8':  case '9':
9279       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9280       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9281       case 'C':  case 'W':             case 'c':  case 'w': 
9282         fenMode = TRUE;
9283         break;
9284     }
9285 #else
9286     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9287     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9288 #endif
9289
9290     if (pn >= 2) {
9291         if (fenMode || line[0] == '#') pn--;
9292         while (pn > 0) {
9293             /* skip positions before number pn */
9294             if (fgets(line, MSG_SIZ, f) == NULL) {
9295                 Reset(TRUE, TRUE);
9296                 DisplayError(_("Position not found in file"), 0);
9297                 return FALSE;
9298             }
9299             if (fenMode || line[0] == '#') pn--;
9300         }
9301     }
9302
9303     if (fenMode) {
9304         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9305             DisplayError(_("Bad FEN position in file"), 0);
9306             return FALSE;
9307         }
9308     } else {
9309         (void) fgets(line, MSG_SIZ, f);
9310         (void) fgets(line, MSG_SIZ, f);
9311     
9312         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9313             (void) fgets(line, MSG_SIZ, f);
9314             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9315                 if (*p == ' ')
9316                   continue;
9317                 initial_position[i][j++] = CharToPiece(*p);
9318             }
9319         }
9320     
9321         blackPlaysFirst = FALSE;
9322         if (!feof(f)) {
9323             (void) fgets(line, MSG_SIZ, f);
9324             if (strncmp(line, "black", strlen("black"))==0)
9325               blackPlaysFirst = TRUE;
9326         }
9327     }
9328     startedFromSetupPosition = TRUE;
9329     
9330     SendToProgram("force\n", &first);
9331     CopyBoard(boards[0], initial_position);
9332     if (blackPlaysFirst) {
9333         currentMove = forwardMostMove = backwardMostMove = 1;
9334         strcpy(moveList[0], "");
9335         strcpy(parseList[0], "");
9336         CopyBoard(boards[1], initial_position);
9337         DisplayMessage("", _("Black to play"));
9338     } else {
9339         currentMove = forwardMostMove = backwardMostMove = 0;
9340         DisplayMessage("", _("White to play"));
9341     }
9342           /* [HGM] copy FEN attributes as well */
9343           {   int i;
9344               initialRulePlies = FENrulePlies;
9345               epStatus[forwardMostMove] = FENepStatus;
9346               for( i=0; i< nrCastlingRights; i++ )
9347                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9348           }
9349     SendBoard(&first, forwardMostMove);
9350     if (appData.debugMode) {
9351 int i, j;
9352   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9353   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9354         fprintf(debugFP, "Load Position\n");
9355     }
9356
9357     if (positionNumber > 1) {
9358         sprintf(line, "%s %d", title, positionNumber);
9359         DisplayTitle(line);
9360     } else {
9361         DisplayTitle(title);
9362     }
9363     gameMode = EditGame;
9364     ModeHighlight();
9365     ResetClocks();
9366     timeRemaining[0][1] = whiteTimeRemaining;
9367     timeRemaining[1][1] = blackTimeRemaining;
9368     DrawPosition(FALSE, boards[currentMove]);
9369    
9370     return TRUE;
9371 }
9372
9373
9374 void
9375 CopyPlayerNameIntoFileName(dest, src)
9376      char **dest, *src;
9377 {
9378     while (*src != NULLCHAR && *src != ',') {
9379         if (*src == ' ') {
9380             *(*dest)++ = '_';
9381             src++;
9382         } else {
9383             *(*dest)++ = *src++;
9384         }
9385     }
9386 }
9387
9388 char *DefaultFileName(ext)
9389      char *ext;
9390 {
9391     static char def[MSG_SIZ];
9392     char *p;
9393
9394     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9395         p = def;
9396         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9397         *p++ = '-';
9398         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9399         *p++ = '.';
9400         strcpy(p, ext);
9401     } else {
9402         def[0] = NULLCHAR;
9403     }
9404     return def;
9405 }
9406
9407 /* Save the current game to the given file */
9408 int
9409 SaveGameToFile(filename, append)
9410      char *filename;
9411      int append;
9412 {
9413     FILE *f;
9414     char buf[MSG_SIZ];
9415
9416     if (strcmp(filename, "-") == 0) {
9417         return SaveGame(stdout, 0, NULL);
9418     } else {
9419         f = fopen(filename, append ? "a" : "w");
9420         if (f == NULL) {
9421             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9422             DisplayError(buf, errno);
9423             return FALSE;
9424         } else {
9425             return SaveGame(f, 0, NULL);
9426         }
9427     }
9428 }
9429
9430 char *
9431 SavePart(str)
9432      char *str;
9433 {
9434     static char buf[MSG_SIZ];
9435     char *p;
9436     
9437     p = strchr(str, ' ');
9438     if (p == NULL) return str;
9439     strncpy(buf, str, p - str);
9440     buf[p - str] = NULLCHAR;
9441     return buf;
9442 }
9443
9444 #define PGN_MAX_LINE 75
9445
9446 #define PGN_SIDE_WHITE  0
9447 #define PGN_SIDE_BLACK  1
9448
9449 /* [AS] */
9450 static int FindFirstMoveOutOfBook( int side )
9451 {
9452     int result = -1;
9453
9454     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9455         int index = backwardMostMove;
9456         int has_book_hit = 0;
9457
9458         if( (index % 2) != side ) {
9459             index++;
9460         }
9461
9462         while( index < forwardMostMove ) {
9463             /* Check to see if engine is in book */
9464             int depth = pvInfoList[index].depth;
9465             int score = pvInfoList[index].score;
9466             int in_book = 0;
9467
9468             if( depth <= 2 ) {
9469                 in_book = 1;
9470             }
9471             else if( score == 0 && depth == 63 ) {
9472                 in_book = 1; /* Zappa */
9473             }
9474             else if( score == 2 && depth == 99 ) {
9475                 in_book = 1; /* Abrok */
9476             }
9477
9478             has_book_hit += in_book;
9479
9480             if( ! in_book ) {
9481                 result = index;
9482
9483                 break;
9484             }
9485
9486             index += 2;
9487         }
9488     }
9489
9490     return result;
9491 }
9492
9493 /* [AS] */
9494 void GetOutOfBookInfo( char * buf )
9495 {
9496     int oob[2];
9497     int i;
9498     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9499
9500     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9501     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9502
9503     *buf = '\0';
9504
9505     if( oob[0] >= 0 || oob[1] >= 0 ) {
9506         for( i=0; i<2; i++ ) {
9507             int idx = oob[i];
9508
9509             if( idx >= 0 ) {
9510                 if( i > 0 && oob[0] >= 0 ) {
9511                     strcat( buf, "   " );
9512                 }
9513
9514                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9515                 sprintf( buf+strlen(buf), "%s%.2f", 
9516                     pvInfoList[idx].score >= 0 ? "+" : "",
9517                     pvInfoList[idx].score / 100.0 );
9518             }
9519         }
9520     }
9521 }
9522
9523 /* Save game in PGN style and close the file */
9524 int
9525 SaveGamePGN(f)
9526      FILE *f;
9527 {
9528     int i, offset, linelen, newblock;
9529     time_t tm;
9530 //    char *movetext;
9531     char numtext[32];
9532     int movelen, numlen, blank;
9533     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9534
9535     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9536     
9537     tm = time((time_t *) NULL);
9538     
9539     PrintPGNTags(f, &gameInfo);
9540     
9541     if (backwardMostMove > 0 || startedFromSetupPosition) {
9542         char *fen = PositionToFEN(backwardMostMove, NULL);
9543         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9544         fprintf(f, "\n{--------------\n");
9545         PrintPosition(f, backwardMostMove);
9546         fprintf(f, "--------------}\n");
9547         free(fen);
9548     }
9549     else {
9550         /* [AS] Out of book annotation */
9551         if( appData.saveOutOfBookInfo ) {
9552             char buf[64];
9553
9554             GetOutOfBookInfo( buf );
9555
9556             if( buf[0] != '\0' ) {
9557                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9558             }
9559         }
9560
9561         fprintf(f, "\n");
9562     }
9563
9564     i = backwardMostMove;
9565     linelen = 0;
9566     newblock = TRUE;
9567
9568     while (i < forwardMostMove) {
9569         /* Print comments preceding this move */
9570         if (commentList[i] != NULL) {
9571             if (linelen > 0) fprintf(f, "\n");
9572             fprintf(f, "{\n%s}\n", commentList[i]);
9573             linelen = 0;
9574             newblock = TRUE;
9575         }
9576
9577         /* Format move number */
9578         if ((i % 2) == 0) {
9579             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9580         } else {
9581             if (newblock) {
9582                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9583             } else {
9584                 numtext[0] = NULLCHAR;
9585             }
9586         }
9587         numlen = strlen(numtext);
9588         newblock = FALSE;
9589
9590         /* Print move number */
9591         blank = linelen > 0 && numlen > 0;
9592         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9593             fprintf(f, "\n");
9594             linelen = 0;
9595             blank = 0;
9596         }
9597         if (blank) {
9598             fprintf(f, " ");
9599             linelen++;
9600         }
9601         fprintf(f, numtext);
9602         linelen += numlen;
9603
9604         /* Get move */
9605         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9606         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9607         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9608                 int p = movelen - 1;
9609                 if(move_buffer[p] == ' ') p--;
9610                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9611                     while(p && move_buffer[--p] != '(');
9612                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9613                 }
9614         }
9615
9616         /* Print move */
9617         blank = linelen > 0 && movelen > 0;
9618         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9619             fprintf(f, "\n");
9620             linelen = 0;
9621             blank = 0;
9622         }
9623         if (blank) {
9624             fprintf(f, " ");
9625             linelen++;
9626         }
9627         fprintf(f, move_buffer);
9628         linelen += movelen;
9629
9630         /* [AS] Add PV info if present */
9631         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9632             /* [HGM] add time */
9633             char buf[MSG_SIZ]; int seconds = 0;
9634
9635 #if 1
9636             if(i >= backwardMostMove) {
9637                 if(WhiteOnMove(i))
9638                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9639                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9640                 else
9641                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9642                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9643             }
9644             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9645 #else
9646             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9647 #endif
9648
9649             if( seconds <= 0) buf[0] = 0; else
9650             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9651                 seconds = (seconds + 4)/10; // round to full seconds
9652                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9653                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9654             }
9655
9656             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9657                 pvInfoList[i].score >= 0 ? "+" : "",
9658                 pvInfoList[i].score / 100.0,
9659                 pvInfoList[i].depth,
9660                 buf );
9661
9662             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9663
9664             /* Print score/depth */
9665             blank = linelen > 0 && movelen > 0;
9666             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9667                 fprintf(f, "\n");
9668                 linelen = 0;
9669                 blank = 0;
9670             }
9671             if (blank) {
9672                 fprintf(f, " ");
9673                 linelen++;
9674             }
9675             fprintf(f, move_buffer);
9676             linelen += movelen;
9677         }
9678
9679         i++;
9680     }
9681     
9682     /* Start a new line */
9683     if (linelen > 0) fprintf(f, "\n");
9684
9685     /* Print comments after last move */
9686     if (commentList[i] != NULL) {
9687         fprintf(f, "{\n%s}\n", commentList[i]);
9688     }
9689
9690     /* Print result */
9691     if (gameInfo.resultDetails != NULL &&
9692         gameInfo.resultDetails[0] != NULLCHAR) {
9693         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9694                 PGNResult(gameInfo.result));
9695     } else {
9696         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9697     }
9698
9699     fclose(f);
9700     return TRUE;
9701 }
9702
9703 /* Save game in old style and close the file */
9704 int
9705 SaveGameOldStyle(f)
9706      FILE *f;
9707 {
9708     int i, offset;
9709     time_t tm;
9710     
9711     tm = time((time_t *) NULL);
9712     
9713     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9714     PrintOpponents(f);
9715     
9716     if (backwardMostMove > 0 || startedFromSetupPosition) {
9717         fprintf(f, "\n[--------------\n");
9718         PrintPosition(f, backwardMostMove);
9719         fprintf(f, "--------------]\n");
9720     } else {
9721         fprintf(f, "\n");
9722     }
9723
9724     i = backwardMostMove;
9725     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9726
9727     while (i < forwardMostMove) {
9728         if (commentList[i] != NULL) {
9729             fprintf(f, "[%s]\n", commentList[i]);
9730         }
9731
9732         if ((i % 2) == 1) {
9733             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9734             i++;
9735         } else {
9736             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9737             i++;
9738             if (commentList[i] != NULL) {
9739                 fprintf(f, "\n");
9740                 continue;
9741             }
9742             if (i >= forwardMostMove) {
9743                 fprintf(f, "\n");
9744                 break;
9745             }
9746             fprintf(f, "%s\n", parseList[i]);
9747             i++;
9748         }
9749     }
9750     
9751     if (commentList[i] != NULL) {
9752         fprintf(f, "[%s]\n", commentList[i]);
9753     }
9754
9755     /* This isn't really the old style, but it's close enough */
9756     if (gameInfo.resultDetails != NULL &&
9757         gameInfo.resultDetails[0] != NULLCHAR) {
9758         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9759                 gameInfo.resultDetails);
9760     } else {
9761         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9762     }
9763
9764     fclose(f);
9765     return TRUE;
9766 }
9767
9768 /* Save the current game to open file f and close the file */
9769 int
9770 SaveGame(f, dummy, dummy2)
9771      FILE *f;
9772      int dummy;
9773      char *dummy2;
9774 {
9775     if (gameMode == EditPosition) EditPositionDone();
9776     if (appData.oldSaveStyle)
9777       return SaveGameOldStyle(f);
9778     else
9779       return SaveGamePGN(f);
9780 }
9781
9782 /* Save the current position to the given file */
9783 int
9784 SavePositionToFile(filename)
9785      char *filename;
9786 {
9787     FILE *f;
9788     char buf[MSG_SIZ];
9789
9790     if (strcmp(filename, "-") == 0) {
9791         return SavePosition(stdout, 0, NULL);
9792     } else {
9793         f = fopen(filename, "a");
9794         if (f == NULL) {
9795             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9796             DisplayError(buf, errno);
9797             return FALSE;
9798         } else {
9799             SavePosition(f, 0, NULL);
9800             return TRUE;
9801         }
9802     }
9803 }
9804
9805 /* Save the current position to the given open file and close the file */
9806 int
9807 SavePosition(f, dummy, dummy2)
9808      FILE *f;
9809      int dummy;
9810      char *dummy2;
9811 {
9812     time_t tm;
9813     char *fen;
9814     
9815     if (appData.oldSaveStyle) {
9816         tm = time((time_t *) NULL);
9817     
9818         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9819         PrintOpponents(f);
9820         fprintf(f, "[--------------\n");
9821         PrintPosition(f, currentMove);
9822         fprintf(f, "--------------]\n");
9823     } else {
9824         fen = PositionToFEN(currentMove, NULL);
9825         fprintf(f, "%s\n", fen);
9826         free(fen);
9827     }
9828     fclose(f);
9829     return TRUE;
9830 }
9831
9832 void
9833 ReloadCmailMsgEvent(unregister)
9834      int unregister;
9835 {
9836 #if !WIN32
9837     static char *inFilename = NULL;
9838     static char *outFilename;
9839     int i;
9840     struct stat inbuf, outbuf;
9841     int status;
9842     
9843     /* Any registered moves are unregistered if unregister is set, */
9844     /* i.e. invoked by the signal handler */
9845     if (unregister) {
9846         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9847             cmailMoveRegistered[i] = FALSE;
9848             if (cmailCommentList[i] != NULL) {
9849                 free(cmailCommentList[i]);
9850                 cmailCommentList[i] = NULL;
9851             }
9852         }
9853         nCmailMovesRegistered = 0;
9854     }
9855
9856     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9857         cmailResult[i] = CMAIL_NOT_RESULT;
9858     }
9859     nCmailResults = 0;
9860
9861     if (inFilename == NULL) {
9862         /* Because the filenames are static they only get malloced once  */
9863         /* and they never get freed                                      */
9864         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9865         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9866
9867         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9868         sprintf(outFilename, "%s.out", appData.cmailGameName);
9869     }
9870     
9871     status = stat(outFilename, &outbuf);
9872     if (status < 0) {
9873         cmailMailedMove = FALSE;
9874     } else {
9875         status = stat(inFilename, &inbuf);
9876         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9877     }
9878     
9879     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9880        counts the games, notes how each one terminated, etc.
9881        
9882        It would be nice to remove this kludge and instead gather all
9883        the information while building the game list.  (And to keep it
9884        in the game list nodes instead of having a bunch of fixed-size
9885        parallel arrays.)  Note this will require getting each game's
9886        termination from the PGN tags, as the game list builder does
9887        not process the game moves.  --mann
9888        */
9889     cmailMsgLoaded = TRUE;
9890     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9891     
9892     /* Load first game in the file or popup game menu */
9893     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9894
9895 #endif /* !WIN32 */
9896     return;
9897 }
9898
9899 int
9900 RegisterMove()
9901 {
9902     FILE *f;
9903     char string[MSG_SIZ];
9904
9905     if (   cmailMailedMove
9906         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9907         return TRUE;            /* Allow free viewing  */
9908     }
9909
9910     /* Unregister move to ensure that we don't leave RegisterMove        */
9911     /* with the move registered when the conditions for registering no   */
9912     /* longer hold                                                       */
9913     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9914         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9915         nCmailMovesRegistered --;
9916
9917         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9918           {
9919               free(cmailCommentList[lastLoadGameNumber - 1]);
9920               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9921           }
9922     }
9923
9924     if (cmailOldMove == -1) {
9925         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9926         return FALSE;
9927     }
9928
9929     if (currentMove > cmailOldMove + 1) {
9930         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9931         return FALSE;
9932     }
9933
9934     if (currentMove < cmailOldMove) {
9935         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9936         return FALSE;
9937     }
9938
9939     if (forwardMostMove > currentMove) {
9940         /* Silently truncate extra moves */
9941         TruncateGame();
9942     }
9943
9944     if (   (currentMove == cmailOldMove + 1)
9945         || (   (currentMove == cmailOldMove)
9946             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9947                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9948         if (gameInfo.result != GameUnfinished) {
9949             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9950         }
9951
9952         if (commentList[currentMove] != NULL) {
9953             cmailCommentList[lastLoadGameNumber - 1]
9954               = StrSave(commentList[currentMove]);
9955         }
9956         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9957
9958         if (appData.debugMode)
9959           fprintf(debugFP, "Saving %s for game %d\n",
9960                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9961
9962         sprintf(string,
9963                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9964         
9965         f = fopen(string, "w");
9966         if (appData.oldSaveStyle) {
9967             SaveGameOldStyle(f); /* also closes the file */
9968             
9969             sprintf(string, "%s.pos.out", appData.cmailGameName);
9970             f = fopen(string, "w");
9971             SavePosition(f, 0, NULL); /* also closes the file */
9972         } else {
9973             fprintf(f, "{--------------\n");
9974             PrintPosition(f, currentMove);
9975             fprintf(f, "--------------}\n\n");
9976             
9977             SaveGame(f, 0, NULL); /* also closes the file*/
9978         }
9979         
9980         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9981         nCmailMovesRegistered ++;
9982     } else if (nCmailGames == 1) {
9983         DisplayError(_("You have not made a move yet"), 0);
9984         return FALSE;
9985     }
9986
9987     return TRUE;
9988 }
9989
9990 void
9991 MailMoveEvent()
9992 {
9993 #if !WIN32
9994     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9995     FILE *commandOutput;
9996     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9997     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9998     int nBuffers;
9999     int i;
10000     int archived;
10001     char *arcDir;
10002
10003     if (! cmailMsgLoaded) {
10004         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10005         return;
10006     }
10007
10008     if (nCmailGames == nCmailResults) {
10009         DisplayError(_("No unfinished games"), 0);
10010         return;
10011     }
10012
10013 #if CMAIL_PROHIBIT_REMAIL
10014     if (cmailMailedMove) {
10015         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);
10016         DisplayError(msg, 0);
10017         return;
10018     }
10019 #endif
10020
10021     if (! (cmailMailedMove || RegisterMove())) return;
10022     
10023     if (   cmailMailedMove
10024         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10025         sprintf(string, partCommandString,
10026                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10027         commandOutput = popen(string, "r");
10028
10029         if (commandOutput == NULL) {
10030             DisplayError(_("Failed to invoke cmail"), 0);
10031         } else {
10032             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10033                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10034             }
10035             if (nBuffers > 1) {
10036                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10037                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10038                 nBytes = MSG_SIZ - 1;
10039             } else {
10040                 (void) memcpy(msg, buffer, nBytes);
10041             }
10042             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10043
10044             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10045                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10046
10047                 archived = TRUE;
10048                 for (i = 0; i < nCmailGames; i ++) {
10049                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10050                         archived = FALSE;
10051                     }
10052                 }
10053                 if (   archived
10054                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10055                         != NULL)) {
10056                     sprintf(buffer, "%s/%s.%s.archive",
10057                             arcDir,
10058                             appData.cmailGameName,
10059                             gameInfo.date);
10060                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10061                     cmailMsgLoaded = FALSE;
10062                 }
10063             }
10064
10065             DisplayInformation(msg);
10066             pclose(commandOutput);
10067         }
10068     } else {
10069         if ((*cmailMsg) != '\0') {
10070             DisplayInformation(cmailMsg);
10071         }
10072     }
10073
10074     return;
10075 #endif /* !WIN32 */
10076 }
10077
10078 char *
10079 CmailMsg()
10080 {
10081 #if WIN32
10082     return NULL;
10083 #else
10084     int  prependComma = 0;
10085     char number[5];
10086     char string[MSG_SIZ];       /* Space for game-list */
10087     int  i;
10088     
10089     if (!cmailMsgLoaded) return "";
10090
10091     if (cmailMailedMove) {
10092         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10093     } else {
10094         /* Create a list of games left */
10095         sprintf(string, "[");
10096         for (i = 0; i < nCmailGames; i ++) {
10097             if (! (   cmailMoveRegistered[i]
10098                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10099                 if (prependComma) {
10100                     sprintf(number, ",%d", i + 1);
10101                 } else {
10102                     sprintf(number, "%d", i + 1);
10103                     prependComma = 1;
10104                 }
10105                 
10106                 strcat(string, number);
10107             }
10108         }
10109         strcat(string, "]");
10110
10111         if (nCmailMovesRegistered + nCmailResults == 0) {
10112             switch (nCmailGames) {
10113               case 1:
10114                 sprintf(cmailMsg,
10115                         _("Still need to make move for game\n"));
10116                 break;
10117                 
10118               case 2:
10119                 sprintf(cmailMsg,
10120                         _("Still need to make moves for both games\n"));
10121                 break;
10122                 
10123               default:
10124                 sprintf(cmailMsg,
10125                         _("Still need to make moves for all %d games\n"),
10126                         nCmailGames);
10127                 break;
10128             }
10129         } else {
10130             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10131               case 1:
10132                 sprintf(cmailMsg,
10133                         _("Still need to make a move for game %s\n"),
10134                         string);
10135                 break;
10136                 
10137               case 0:
10138                 if (nCmailResults == nCmailGames) {
10139                     sprintf(cmailMsg, _("No unfinished games\n"));
10140                 } else {
10141                     sprintf(cmailMsg, _("Ready to send mail\n"));
10142                 }
10143                 break;
10144                 
10145               default:
10146                 sprintf(cmailMsg,
10147                         _("Still need to make moves for games %s\n"),
10148                         string);
10149             }
10150         }
10151     }
10152     return cmailMsg;
10153 #endif /* WIN32 */
10154 }
10155
10156 void
10157 ResetGameEvent()
10158 {
10159     if (gameMode == Training)
10160       SetTrainingModeOff();
10161
10162     Reset(TRUE, TRUE);
10163     cmailMsgLoaded = FALSE;
10164     if (appData.icsActive) {
10165       SendToICS(ics_prefix);
10166       SendToICS("refresh\n");
10167     }
10168 }
10169
10170 void
10171 ExitEvent(status)
10172      int status;
10173 {
10174     exiting++;
10175     if (exiting > 2) {
10176       /* Give up on clean exit */
10177       exit(status);
10178     }
10179     if (exiting > 1) {
10180       /* Keep trying for clean exit */
10181       return;
10182     }
10183
10184     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10185
10186     if (telnetISR != NULL) {
10187       RemoveInputSource(telnetISR);
10188     }
10189     if (icsPR != NoProc) {
10190       DestroyChildProcess(icsPR, TRUE);
10191     }
10192 #if 0
10193     /* Save game if resource set and not already saved by GameEnds() */
10194     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10195                              && forwardMostMove > 0) {
10196       if (*appData.saveGameFile != NULLCHAR) {
10197         SaveGameToFile(appData.saveGameFile, TRUE);
10198       } else if (appData.autoSaveGames) {
10199         AutoSaveGame();
10200       }
10201       if (*appData.savePositionFile != NULLCHAR) {
10202         SavePositionToFile(appData.savePositionFile);
10203       }
10204     }
10205     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10206 #else
10207     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10208     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10209 #endif
10210     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10211     /* make sure this other one finishes before killing it!                  */
10212     if(endingGame) { int count = 0;
10213         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10214         while(endingGame && count++ < 10) DoSleep(1);
10215         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10216     }
10217
10218     /* Kill off chess programs */
10219     if (first.pr != NoProc) {
10220         ExitAnalyzeMode();
10221         
10222         DoSleep( appData.delayBeforeQuit );
10223         SendToProgram("quit\n", &first);
10224         DoSleep( appData.delayAfterQuit );
10225         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10226     }
10227     if (second.pr != NoProc) {
10228         DoSleep( appData.delayBeforeQuit );
10229         SendToProgram("quit\n", &second);
10230         DoSleep( appData.delayAfterQuit );
10231         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10232     }
10233     if (first.isr != NULL) {
10234         RemoveInputSource(first.isr);
10235     }
10236     if (second.isr != NULL) {
10237         RemoveInputSource(second.isr);
10238     }
10239
10240     ShutDownFrontEnd();
10241     exit(status);
10242 }
10243
10244 void
10245 PauseEvent()
10246 {
10247     if (appData.debugMode)
10248         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10249     if (pausing) {
10250         pausing = FALSE;
10251         ModeHighlight();
10252         if (gameMode == MachinePlaysWhite ||
10253             gameMode == MachinePlaysBlack) {
10254             StartClocks();
10255         } else {
10256             DisplayBothClocks();
10257         }
10258         if (gameMode == PlayFromGameFile) {
10259             if (appData.timeDelay >= 0) 
10260                 AutoPlayGameLoop();
10261         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10262             Reset(FALSE, TRUE);
10263             SendToICS(ics_prefix);
10264             SendToICS("refresh\n");
10265         } else if (currentMove < forwardMostMove) {
10266             ForwardInner(forwardMostMove);
10267         }
10268         pauseExamInvalid = FALSE;
10269     } else {
10270         switch (gameMode) {
10271           default:
10272             return;
10273           case IcsExamining:
10274             pauseExamForwardMostMove = forwardMostMove;
10275             pauseExamInvalid = FALSE;
10276             /* fall through */
10277           case IcsObserving:
10278           case IcsPlayingWhite:
10279           case IcsPlayingBlack:
10280             pausing = TRUE;
10281             ModeHighlight();
10282             return;
10283           case PlayFromGameFile:
10284             (void) StopLoadGameTimer();
10285             pausing = TRUE;
10286             ModeHighlight();
10287             break;
10288           case BeginningOfGame:
10289             if (appData.icsActive) return;
10290             /* else fall through */
10291           case MachinePlaysWhite:
10292           case MachinePlaysBlack:
10293           case TwoMachinesPlay:
10294             if (forwardMostMove == 0)
10295               return;           /* don't pause if no one has moved */
10296             if ((gameMode == MachinePlaysWhite &&
10297                  !WhiteOnMove(forwardMostMove)) ||
10298                 (gameMode == MachinePlaysBlack &&
10299                  WhiteOnMove(forwardMostMove))) {
10300                 StopClocks();
10301             }
10302             pausing = TRUE;
10303             ModeHighlight();
10304             break;
10305         }
10306     }
10307 }
10308
10309 void
10310 EditCommentEvent()
10311 {
10312     char title[MSG_SIZ];
10313
10314     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10315         strcpy(title, _("Edit comment"));
10316     } else {
10317         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10318                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10319                 parseList[currentMove - 1]);
10320     }
10321
10322     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10323 }
10324
10325
10326 void
10327 EditTagsEvent()
10328 {
10329     char *tags = PGNTags(&gameInfo);
10330     EditTagsPopUp(tags);
10331     free(tags);
10332 }
10333
10334 void
10335 AnalyzeModeEvent()
10336 {
10337     if (appData.noChessProgram || gameMode == AnalyzeMode)
10338       return;
10339
10340     if (gameMode != AnalyzeFile) {
10341         if (!appData.icsEngineAnalyze) {
10342                EditGameEvent();
10343                if (gameMode != EditGame) return;
10344         }
10345         ResurrectChessProgram();
10346         SendToProgram("analyze\n", &first);
10347         first.analyzing = TRUE;
10348         /*first.maybeThinking = TRUE;*/
10349         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10350         AnalysisPopUp(_("Analysis"),
10351                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10352     }
10353     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10354     pausing = FALSE;
10355     ModeHighlight();
10356     SetGameInfo();
10357
10358     StartAnalysisClock();
10359     GetTimeMark(&lastNodeCountTime);
10360     lastNodeCount = 0;
10361 }
10362
10363 void
10364 AnalyzeFileEvent()
10365 {
10366     if (appData.noChessProgram || gameMode == AnalyzeFile)
10367       return;
10368
10369     if (gameMode != AnalyzeMode) {
10370         EditGameEvent();
10371         if (gameMode != EditGame) return;
10372         ResurrectChessProgram();
10373         SendToProgram("analyze\n", &first);
10374         first.analyzing = TRUE;
10375         /*first.maybeThinking = TRUE;*/
10376         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10377         AnalysisPopUp(_("Analysis"),
10378                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10379     }
10380     gameMode = AnalyzeFile;
10381     pausing = FALSE;
10382     ModeHighlight();
10383     SetGameInfo();
10384
10385     StartAnalysisClock();
10386     GetTimeMark(&lastNodeCountTime);
10387     lastNodeCount = 0;
10388 }
10389
10390 void
10391 MachineWhiteEvent()
10392 {
10393     char buf[MSG_SIZ];
10394     char *bookHit = NULL;
10395
10396     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10397       return;
10398
10399
10400     if (gameMode == PlayFromGameFile || 
10401         gameMode == TwoMachinesPlay  || 
10402         gameMode == Training         || 
10403         gameMode == AnalyzeMode      || 
10404         gameMode == EndOfGame)
10405         EditGameEvent();
10406
10407     if (gameMode == EditPosition) 
10408         EditPositionDone();
10409
10410     if (!WhiteOnMove(currentMove)) {
10411         DisplayError(_("It is not White's turn"), 0);
10412         return;
10413     }
10414   
10415     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10416       ExitAnalyzeMode();
10417
10418     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10419         gameMode == AnalyzeFile)
10420         TruncateGame();
10421
10422     ResurrectChessProgram();    /* in case it isn't running */
10423     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10424         gameMode = MachinePlaysWhite;
10425         ResetClocks();
10426     } else
10427     gameMode = MachinePlaysWhite;
10428     pausing = FALSE;
10429     ModeHighlight();
10430     SetGameInfo();
10431     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10432     DisplayTitle(buf);
10433     if (first.sendName) {
10434       sprintf(buf, "name %s\n", gameInfo.black);
10435       SendToProgram(buf, &first);
10436     }
10437     if (first.sendTime) {
10438       if (first.useColors) {
10439         SendToProgram("black\n", &first); /*gnu kludge*/
10440       }
10441       SendTimeRemaining(&first, TRUE);
10442     }
10443     if (first.useColors) {
10444       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10445     }
10446     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10447     SetMachineThinkingEnables();
10448     first.maybeThinking = TRUE;
10449     StartClocks();
10450
10451     if (appData.autoFlipView && !flipView) {
10452       flipView = !flipView;
10453       DrawPosition(FALSE, NULL);
10454       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10455     }
10456
10457     if(bookHit) { // [HGM] book: simulate book reply
10458         static char bookMove[MSG_SIZ]; // a bit generous?
10459
10460         programStats.nodes = programStats.depth = programStats.time = 
10461         programStats.score = programStats.got_only_move = 0;
10462         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10463
10464         strcpy(bookMove, "move ");
10465         strcat(bookMove, bookHit);
10466         HandleMachineMove(bookMove, &first);
10467     }
10468 }
10469
10470 void
10471 MachineBlackEvent()
10472 {
10473     char buf[MSG_SIZ];
10474    char *bookHit = NULL;
10475
10476     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10477         return;
10478
10479
10480     if (gameMode == PlayFromGameFile || 
10481         gameMode == TwoMachinesPlay  || 
10482         gameMode == Training         || 
10483         gameMode == AnalyzeMode      || 
10484         gameMode == EndOfGame)
10485         EditGameEvent();
10486
10487     if (gameMode == EditPosition) 
10488         EditPositionDone();
10489
10490     if (WhiteOnMove(currentMove)) {
10491         DisplayError(_("It is not Black's turn"), 0);
10492         return;
10493     }
10494     
10495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10496       ExitAnalyzeMode();
10497
10498     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10499         gameMode == AnalyzeFile)
10500         TruncateGame();
10501
10502     ResurrectChessProgram();    /* in case it isn't running */
10503     gameMode = MachinePlaysBlack;
10504     pausing = FALSE;
10505     ModeHighlight();
10506     SetGameInfo();
10507     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10508     DisplayTitle(buf);
10509     if (first.sendName) {
10510       sprintf(buf, "name %s\n", gameInfo.white);
10511       SendToProgram(buf, &first);
10512     }
10513     if (first.sendTime) {
10514       if (first.useColors) {
10515         SendToProgram("white\n", &first); /*gnu kludge*/
10516       }
10517       SendTimeRemaining(&first, FALSE);
10518     }
10519     if (first.useColors) {
10520       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10521     }
10522     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10523     SetMachineThinkingEnables();
10524     first.maybeThinking = TRUE;
10525     StartClocks();
10526
10527     if (appData.autoFlipView && flipView) {
10528       flipView = !flipView;
10529       DrawPosition(FALSE, NULL);
10530       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10531     }
10532     if(bookHit) { // [HGM] book: simulate book reply
10533         static char bookMove[MSG_SIZ]; // a bit generous?
10534
10535         programStats.nodes = programStats.depth = programStats.time = 
10536         programStats.score = programStats.got_only_move = 0;
10537         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10538
10539         strcpy(bookMove, "move ");
10540         strcat(bookMove, bookHit);
10541         HandleMachineMove(bookMove, &first);
10542     }
10543 }
10544
10545
10546 void
10547 DisplayTwoMachinesTitle()
10548 {
10549     char buf[MSG_SIZ];
10550     if (appData.matchGames > 0) {
10551         if (first.twoMachinesColor[0] == 'w') {
10552             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10553                     gameInfo.white, gameInfo.black,
10554                     first.matchWins, second.matchWins,
10555                     matchGame - 1 - (first.matchWins + second.matchWins));
10556         } else {
10557             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10558                     gameInfo.white, gameInfo.black,
10559                     second.matchWins, first.matchWins,
10560                     matchGame - 1 - (first.matchWins + second.matchWins));
10561         }
10562     } else {
10563         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10564     }
10565     DisplayTitle(buf);
10566 }
10567
10568 void
10569 TwoMachinesEvent P((void))
10570 {
10571     int i;
10572     char buf[MSG_SIZ];
10573     ChessProgramState *onmove;
10574     char *bookHit = NULL;
10575     
10576     if (appData.noChessProgram) return;
10577
10578     switch (gameMode) {
10579       case TwoMachinesPlay:
10580         return;
10581       case MachinePlaysWhite:
10582       case MachinePlaysBlack:
10583         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10584             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10585             return;
10586         }
10587         /* fall through */
10588       case BeginningOfGame:
10589       case PlayFromGameFile:
10590       case EndOfGame:
10591         EditGameEvent();
10592         if (gameMode != EditGame) return;
10593         break;
10594       case EditPosition:
10595         EditPositionDone();
10596         break;
10597       case AnalyzeMode:
10598       case AnalyzeFile:
10599         ExitAnalyzeMode();
10600         break;
10601       case EditGame:
10602       default:
10603         break;
10604     }
10605
10606     forwardMostMove = currentMove;
10607     ResurrectChessProgram();    /* in case first program isn't running */
10608
10609     if (second.pr == NULL) {
10610         StartChessProgram(&second);
10611         if (second.protocolVersion == 1) {
10612           TwoMachinesEventIfReady();
10613         } else {
10614           /* kludge: allow timeout for initial "feature" command */
10615           FreezeUI();
10616           DisplayMessage("", _("Starting second chess program"));
10617           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10618         }
10619         return;
10620     }
10621     DisplayMessage("", "");
10622     InitChessProgram(&second, FALSE);
10623     SendToProgram("force\n", &second);
10624     if (startedFromSetupPosition) {
10625         SendBoard(&second, backwardMostMove);
10626     if (appData.debugMode) {
10627         fprintf(debugFP, "Two Machines\n");
10628     }
10629     }
10630     for (i = backwardMostMove; i < forwardMostMove; i++) {
10631         SendMoveToProgram(i, &second);
10632     }
10633
10634     gameMode = TwoMachinesPlay;
10635     pausing = FALSE;
10636     ModeHighlight();
10637     SetGameInfo();
10638     DisplayTwoMachinesTitle();
10639     firstMove = TRUE;
10640     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10641         onmove = &first;
10642     } else {
10643         onmove = &second;
10644     }
10645
10646     SendToProgram(first.computerString, &first);
10647     if (first.sendName) {
10648       sprintf(buf, "name %s\n", second.tidy);
10649       SendToProgram(buf, &first);
10650     }
10651     SendToProgram(second.computerString, &second);
10652     if (second.sendName) {
10653       sprintf(buf, "name %s\n", first.tidy);
10654       SendToProgram(buf, &second);
10655     }
10656
10657     ResetClocks();
10658     if (!first.sendTime || !second.sendTime) {
10659         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10660         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10661     }
10662     if (onmove->sendTime) {
10663       if (onmove->useColors) {
10664         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10665       }
10666       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10667     }
10668     if (onmove->useColors) {
10669       SendToProgram(onmove->twoMachinesColor, onmove);
10670     }
10671     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10672 //    SendToProgram("go\n", onmove);
10673     onmove->maybeThinking = TRUE;
10674     SetMachineThinkingEnables();
10675
10676     StartClocks();
10677
10678     if(bookHit) { // [HGM] book: simulate book reply
10679         static char bookMove[MSG_SIZ]; // a bit generous?
10680
10681         programStats.nodes = programStats.depth = programStats.time = 
10682         programStats.score = programStats.got_only_move = 0;
10683         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10684
10685         strcpy(bookMove, "move ");
10686         strcat(bookMove, bookHit);
10687         HandleMachineMove(bookMove, &first);
10688     }
10689 }
10690
10691 void
10692 TrainingEvent()
10693 {
10694     if (gameMode == Training) {
10695       SetTrainingModeOff();
10696       gameMode = PlayFromGameFile;
10697       DisplayMessage("", _("Training mode off"));
10698     } else {
10699       gameMode = Training;
10700       animateTraining = appData.animate;
10701
10702       /* make sure we are not already at the end of the game */
10703       if (currentMove < forwardMostMove) {
10704         SetTrainingModeOn();
10705         DisplayMessage("", _("Training mode on"));
10706       } else {
10707         gameMode = PlayFromGameFile;
10708         DisplayError(_("Already at end of game"), 0);
10709       }
10710     }
10711     ModeHighlight();
10712 }
10713
10714 void
10715 IcsClientEvent()
10716 {
10717     if (!appData.icsActive) return;
10718     switch (gameMode) {
10719       case IcsPlayingWhite:
10720       case IcsPlayingBlack:
10721       case IcsObserving:
10722       case IcsIdle:
10723       case BeginningOfGame:
10724       case IcsExamining:
10725         return;
10726
10727       case EditGame:
10728         break;
10729
10730       case EditPosition:
10731         EditPositionDone();
10732         break;
10733
10734       case AnalyzeMode:
10735       case AnalyzeFile:
10736         ExitAnalyzeMode();
10737         break;
10738         
10739       default:
10740         EditGameEvent();
10741         break;
10742     }
10743
10744     gameMode = IcsIdle;
10745     ModeHighlight();
10746     return;
10747 }
10748
10749
10750 void
10751 EditGameEvent()
10752 {
10753     int i;
10754
10755     switch (gameMode) {
10756       case Training:
10757         SetTrainingModeOff();
10758         break;
10759       case MachinePlaysWhite:
10760       case MachinePlaysBlack:
10761       case BeginningOfGame:
10762         SendToProgram("force\n", &first);
10763         SetUserThinkingEnables();
10764         break;
10765       case PlayFromGameFile:
10766         (void) StopLoadGameTimer();
10767         if (gameFileFP != NULL) {
10768             gameFileFP = NULL;
10769         }
10770         break;
10771       case EditPosition:
10772         EditPositionDone();
10773         break;
10774       case AnalyzeMode:
10775       case AnalyzeFile:
10776         ExitAnalyzeMode();
10777         SendToProgram("force\n", &first);
10778         break;
10779       case TwoMachinesPlay:
10780         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10781         ResurrectChessProgram();
10782         SetUserThinkingEnables();
10783         break;
10784       case EndOfGame:
10785         ResurrectChessProgram();
10786         break;
10787       case IcsPlayingBlack:
10788       case IcsPlayingWhite:
10789         DisplayError(_("Warning: You are still playing a game"), 0);
10790         break;
10791       case IcsObserving:
10792         DisplayError(_("Warning: You are still observing a game"), 0);
10793         break;
10794       case IcsExamining:
10795         DisplayError(_("Warning: You are still examining a game"), 0);
10796         break;
10797       case IcsIdle:
10798         break;
10799       case EditGame:
10800       default:
10801         return;
10802     }
10803     
10804     pausing = FALSE;
10805     StopClocks();
10806     first.offeredDraw = second.offeredDraw = 0;
10807
10808     if (gameMode == PlayFromGameFile) {
10809         whiteTimeRemaining = timeRemaining[0][currentMove];
10810         blackTimeRemaining = timeRemaining[1][currentMove];
10811         DisplayTitle("");
10812     }
10813
10814     if (gameMode == MachinePlaysWhite ||
10815         gameMode == MachinePlaysBlack ||
10816         gameMode == TwoMachinesPlay ||
10817         gameMode == EndOfGame) {
10818         i = forwardMostMove;
10819         while (i > currentMove) {
10820             SendToProgram("undo\n", &first);
10821             i--;
10822         }
10823         whiteTimeRemaining = timeRemaining[0][currentMove];
10824         blackTimeRemaining = timeRemaining[1][currentMove];
10825         DisplayBothClocks();
10826         if (whiteFlag || blackFlag) {
10827             whiteFlag = blackFlag = 0;
10828         }
10829         DisplayTitle("");
10830     }           
10831     
10832     gameMode = EditGame;
10833     ModeHighlight();
10834     SetGameInfo();
10835 }
10836
10837
10838 void
10839 EditPositionEvent()
10840 {
10841     if (gameMode == EditPosition) {
10842         EditGameEvent();
10843         return;
10844     }
10845     
10846     EditGameEvent();
10847     if (gameMode != EditGame) return;
10848     
10849     gameMode = EditPosition;
10850     ModeHighlight();
10851     SetGameInfo();
10852     if (currentMove > 0)
10853       CopyBoard(boards[0], boards[currentMove]);
10854     
10855     blackPlaysFirst = !WhiteOnMove(currentMove);
10856     ResetClocks();
10857     currentMove = forwardMostMove = backwardMostMove = 0;
10858     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10859     DisplayMove(-1);
10860 }
10861
10862 void
10863 ExitAnalyzeMode()
10864 {
10865     /* [DM] icsEngineAnalyze - possible call from other functions */
10866     if (appData.icsEngineAnalyze) {
10867         appData.icsEngineAnalyze = FALSE;
10868
10869         DisplayMessage("",_("Close ICS engine analyze..."));
10870     }
10871     if (first.analysisSupport && first.analyzing) {
10872       SendToProgram("exit\n", &first);
10873       first.analyzing = FALSE;
10874     }
10875     AnalysisPopDown();
10876     thinkOutput[0] = NULLCHAR;
10877 }
10878
10879 void
10880 EditPositionDone()
10881 {
10882     startedFromSetupPosition = TRUE;
10883     InitChessProgram(&first, FALSE);
10884     SendToProgram("force\n", &first);
10885     if (blackPlaysFirst) {
10886         strcpy(moveList[0], "");
10887         strcpy(parseList[0], "");
10888         currentMove = forwardMostMove = backwardMostMove = 1;
10889         CopyBoard(boards[1], boards[0]);
10890         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10891         { int i;
10892           epStatus[1] = epStatus[0];
10893           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10894         }
10895     } else {
10896         currentMove = forwardMostMove = backwardMostMove = 0;
10897     }
10898     SendBoard(&first, forwardMostMove);
10899     if (appData.debugMode) {
10900         fprintf(debugFP, "EditPosDone\n");
10901     }
10902     DisplayTitle("");
10903     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10904     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10905     gameMode = EditGame;
10906     ModeHighlight();
10907     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10908     ClearHighlights(); /* [AS] */
10909 }
10910
10911 /* Pause for `ms' milliseconds */
10912 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10913 void
10914 TimeDelay(ms)
10915      long ms;
10916 {
10917     TimeMark m1, m2;
10918
10919     GetTimeMark(&m1);
10920     do {
10921         GetTimeMark(&m2);
10922     } while (SubtractTimeMarks(&m2, &m1) < ms);
10923 }
10924
10925 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10926 void
10927 SendMultiLineToICS(buf)
10928      char *buf;
10929 {
10930     char temp[MSG_SIZ+1], *p;
10931     int len;
10932
10933     len = strlen(buf);
10934     if (len > MSG_SIZ)
10935       len = MSG_SIZ;
10936   
10937     strncpy(temp, buf, len);
10938     temp[len] = 0;
10939
10940     p = temp;
10941     while (*p) {
10942         if (*p == '\n' || *p == '\r')
10943           *p = ' ';
10944         ++p;
10945     }
10946
10947     strcat(temp, "\n");
10948     SendToICS(temp);
10949     SendToPlayer(temp, strlen(temp));
10950 }
10951
10952 void
10953 SetWhiteToPlayEvent()
10954 {
10955     if (gameMode == EditPosition) {
10956         blackPlaysFirst = FALSE;
10957         DisplayBothClocks();    /* works because currentMove is 0 */
10958     } else if (gameMode == IcsExamining) {
10959         SendToICS(ics_prefix);
10960         SendToICS("tomove white\n");
10961     }
10962 }
10963
10964 void
10965 SetBlackToPlayEvent()
10966 {
10967     if (gameMode == EditPosition) {
10968         blackPlaysFirst = TRUE;
10969         currentMove = 1;        /* kludge */
10970         DisplayBothClocks();
10971         currentMove = 0;
10972     } else if (gameMode == IcsExamining) {
10973         SendToICS(ics_prefix);
10974         SendToICS("tomove black\n");
10975     }
10976 }
10977
10978 void
10979 EditPositionMenuEvent(selection, x, y)
10980      ChessSquare selection;
10981      int x, y;
10982 {
10983     char buf[MSG_SIZ];
10984     ChessSquare piece = boards[0][y][x];
10985
10986     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10987
10988     switch (selection) {
10989       case ClearBoard:
10990         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10991             SendToICS(ics_prefix);
10992             SendToICS("bsetup clear\n");
10993         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10994             SendToICS(ics_prefix);
10995             SendToICS("clearboard\n");
10996         } else {
10997             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10998                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10999                 for (y = 0; y < BOARD_HEIGHT; y++) {
11000                     if (gameMode == IcsExamining) {
11001                         if (boards[currentMove][y][x] != EmptySquare) {
11002                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11003                                     AAA + x, ONE + y);
11004                             SendToICS(buf);
11005                         }
11006                     } else {
11007                         boards[0][y][x] = p;
11008                     }
11009                 }
11010             }
11011         }
11012         if (gameMode == EditPosition) {
11013             DrawPosition(FALSE, boards[0]);
11014         }
11015         break;
11016
11017       case WhitePlay:
11018         SetWhiteToPlayEvent();
11019         break;
11020
11021       case BlackPlay:
11022         SetBlackToPlayEvent();
11023         break;
11024
11025       case EmptySquare:
11026         if (gameMode == IcsExamining) {
11027             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11028             SendToICS(buf);
11029         } else {
11030             boards[0][y][x] = EmptySquare;
11031             DrawPosition(FALSE, boards[0]);
11032         }
11033         break;
11034
11035       case PromotePiece:
11036         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11037            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11038             selection = (ChessSquare) (PROMOTED piece);
11039         } else if(piece == EmptySquare) selection = WhiteSilver;
11040         else selection = (ChessSquare)((int)piece - 1);
11041         goto defaultlabel;
11042
11043       case DemotePiece:
11044         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11045            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11046             selection = (ChessSquare) (DEMOTED piece);
11047         } else if(piece == EmptySquare) selection = BlackSilver;
11048         else selection = (ChessSquare)((int)piece + 1);       
11049         goto defaultlabel;
11050
11051       case WhiteQueen:
11052       case BlackQueen:
11053         if(gameInfo.variant == VariantShatranj ||
11054            gameInfo.variant == VariantXiangqi  ||
11055            gameInfo.variant == VariantCourier    )
11056             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11057         goto defaultlabel;
11058
11059       case WhiteKing:
11060       case BlackKing:
11061         if(gameInfo.variant == VariantXiangqi)
11062             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11063         if(gameInfo.variant == VariantKnightmate)
11064             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11065       default:
11066         defaultlabel:
11067         if (gameMode == IcsExamining) {
11068             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11069                     PieceToChar(selection), AAA + x, ONE + y);
11070             SendToICS(buf);
11071         } else {
11072             boards[0][y][x] = selection;
11073             DrawPosition(FALSE, boards[0]);
11074         }
11075         break;
11076     }
11077 }
11078
11079
11080 void
11081 DropMenuEvent(selection, x, y)
11082      ChessSquare selection;
11083      int x, y;
11084 {
11085     ChessMove moveType;
11086
11087     switch (gameMode) {
11088       case IcsPlayingWhite:
11089       case MachinePlaysBlack:
11090         if (!WhiteOnMove(currentMove)) {
11091             DisplayMoveError(_("It is Black's turn"));
11092             return;
11093         }
11094         moveType = WhiteDrop;
11095         break;
11096       case IcsPlayingBlack:
11097       case MachinePlaysWhite:
11098         if (WhiteOnMove(currentMove)) {
11099             DisplayMoveError(_("It is White's turn"));
11100             return;
11101         }
11102         moveType = BlackDrop;
11103         break;
11104       case EditGame:
11105         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11106         break;
11107       default:
11108         return;
11109     }
11110
11111     if (moveType == BlackDrop && selection < BlackPawn) {
11112       selection = (ChessSquare) ((int) selection
11113                                  + (int) BlackPawn - (int) WhitePawn);
11114     }
11115     if (boards[currentMove][y][x] != EmptySquare) {
11116         DisplayMoveError(_("That square is occupied"));
11117         return;
11118     }
11119
11120     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11121 }
11122
11123 void
11124 AcceptEvent()
11125 {
11126     /* Accept a pending offer of any kind from opponent */
11127     
11128     if (appData.icsActive) {
11129         SendToICS(ics_prefix);
11130         SendToICS("accept\n");
11131     } else if (cmailMsgLoaded) {
11132         if (currentMove == cmailOldMove &&
11133             commentList[cmailOldMove] != NULL &&
11134             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11135                    "Black offers a draw" : "White offers a draw")) {
11136             TruncateGame();
11137             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11139         } else {
11140             DisplayError(_("There is no pending offer on this move"), 0);
11141             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11142         }
11143     } else {
11144         /* Not used for offers from chess program */
11145     }
11146 }
11147
11148 void
11149 DeclineEvent()
11150 {
11151     /* Decline a pending offer of any kind from opponent */
11152     
11153     if (appData.icsActive) {
11154         SendToICS(ics_prefix);
11155         SendToICS("decline\n");
11156     } else if (cmailMsgLoaded) {
11157         if (currentMove == cmailOldMove &&
11158             commentList[cmailOldMove] != NULL &&
11159             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11160                    "Black offers a draw" : "White offers a draw")) {
11161 #ifdef NOTDEF
11162             AppendComment(cmailOldMove, "Draw declined");
11163             DisplayComment(cmailOldMove - 1, "Draw declined");
11164 #endif /*NOTDEF*/
11165         } else {
11166             DisplayError(_("There is no pending offer on this move"), 0);
11167         }
11168     } else {
11169         /* Not used for offers from chess program */
11170     }
11171 }
11172
11173 void
11174 RematchEvent()
11175 {
11176     /* Issue ICS rematch command */
11177     if (appData.icsActive) {
11178         SendToICS(ics_prefix);
11179         SendToICS("rematch\n");
11180     }
11181 }
11182
11183 void
11184 CallFlagEvent()
11185 {
11186     /* Call your opponent's flag (claim a win on time) */
11187     if (appData.icsActive) {
11188         SendToICS(ics_prefix);
11189         SendToICS("flag\n");
11190     } else {
11191         switch (gameMode) {
11192           default:
11193             return;
11194           case MachinePlaysWhite:
11195             if (whiteFlag) {
11196                 if (blackFlag)
11197                   GameEnds(GameIsDrawn, "Both players ran out of time",
11198                            GE_PLAYER);
11199                 else
11200                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11201             } else {
11202                 DisplayError(_("Your opponent is not out of time"), 0);
11203             }
11204             break;
11205           case MachinePlaysBlack:
11206             if (blackFlag) {
11207                 if (whiteFlag)
11208                   GameEnds(GameIsDrawn, "Both players ran out of time",
11209                            GE_PLAYER);
11210                 else
11211                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11212             } else {
11213                 DisplayError(_("Your opponent is not out of time"), 0);
11214             }
11215             break;
11216         }
11217     }
11218 }
11219
11220 void
11221 DrawEvent()
11222 {
11223     /* Offer draw or accept pending draw offer from opponent */
11224     
11225     if (appData.icsActive) {
11226         /* Note: tournament rules require draw offers to be
11227            made after you make your move but before you punch
11228            your clock.  Currently ICS doesn't let you do that;
11229            instead, you immediately punch your clock after making
11230            a move, but you can offer a draw at any time. */
11231         
11232         SendToICS(ics_prefix);
11233         SendToICS("draw\n");
11234     } else if (cmailMsgLoaded) {
11235         if (currentMove == cmailOldMove &&
11236             commentList[cmailOldMove] != NULL &&
11237             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11238                    "Black offers a draw" : "White offers a draw")) {
11239             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11241         } else if (currentMove == cmailOldMove + 1) {
11242             char *offer = WhiteOnMove(cmailOldMove) ?
11243               "White offers a draw" : "Black offers a draw";
11244             AppendComment(currentMove, offer);
11245             DisplayComment(currentMove - 1, offer);
11246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11247         } else {
11248             DisplayError(_("You must make your move before offering a draw"), 0);
11249             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11250         }
11251     } else if (first.offeredDraw) {
11252         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11253     } else {
11254         if (first.sendDrawOffers) {
11255             SendToProgram("draw\n", &first);
11256             userOfferedDraw = TRUE;
11257         }
11258     }
11259 }
11260
11261 void
11262 AdjournEvent()
11263 {
11264     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11265     
11266     if (appData.icsActive) {
11267         SendToICS(ics_prefix);
11268         SendToICS("adjourn\n");
11269     } else {
11270         /* Currently GNU Chess doesn't offer or accept Adjourns */
11271     }
11272 }
11273
11274
11275 void
11276 AbortEvent()
11277 {
11278     /* Offer Abort or accept pending Abort offer from opponent */
11279     
11280     if (appData.icsActive) {
11281         SendToICS(ics_prefix);
11282         SendToICS("abort\n");
11283     } else {
11284         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11285     }
11286 }
11287
11288 void
11289 ResignEvent()
11290 {
11291     /* Resign.  You can do this even if it's not your turn. */
11292     
11293     if (appData.icsActive) {
11294         SendToICS(ics_prefix);
11295         SendToICS("resign\n");
11296     } else {
11297         switch (gameMode) {
11298           case MachinePlaysWhite:
11299             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11300             break;
11301           case MachinePlaysBlack:
11302             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11303             break;
11304           case EditGame:
11305             if (cmailMsgLoaded) {
11306                 TruncateGame();
11307                 if (WhiteOnMove(cmailOldMove)) {
11308                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11309                 } else {
11310                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11311                 }
11312                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11313             }
11314             break;
11315           default:
11316             break;
11317         }
11318     }
11319 }
11320
11321
11322 void
11323 StopObservingEvent()
11324 {
11325     /* Stop observing current games */
11326     SendToICS(ics_prefix);
11327     SendToICS("unobserve\n");
11328 }
11329
11330 void
11331 StopExaminingEvent()
11332 {
11333     /* Stop observing current game */
11334     SendToICS(ics_prefix);
11335     SendToICS("unexamine\n");
11336 }
11337
11338 void
11339 ForwardInner(target)
11340      int target;
11341 {
11342     int limit;
11343
11344     if (appData.debugMode)
11345         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11346                 target, currentMove, forwardMostMove);
11347
11348     if (gameMode == EditPosition)
11349       return;
11350
11351     if (gameMode == PlayFromGameFile && !pausing)
11352       PauseEvent();
11353     
11354     if (gameMode == IcsExamining && pausing)
11355       limit = pauseExamForwardMostMove;
11356     else
11357       limit = forwardMostMove;
11358     
11359     if (target > limit) target = limit;
11360
11361     if (target > 0 && moveList[target - 1][0]) {
11362         int fromX, fromY, toX, toY;
11363         toX = moveList[target - 1][2] - AAA;
11364         toY = moveList[target - 1][3] - ONE;
11365         if (moveList[target - 1][1] == '@') {
11366             if (appData.highlightLastMove) {
11367                 SetHighlights(-1, -1, toX, toY);
11368             }
11369         } else {
11370             fromX = moveList[target - 1][0] - AAA;
11371             fromY = moveList[target - 1][1] - ONE;
11372             if (target == currentMove + 1) {
11373                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11374             }
11375             if (appData.highlightLastMove) {
11376                 SetHighlights(fromX, fromY, toX, toY);
11377             }
11378         }
11379     }
11380     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11381         gameMode == Training || gameMode == PlayFromGameFile || 
11382         gameMode == AnalyzeFile) {
11383         while (currentMove < target) {
11384             SendMoveToProgram(currentMove++, &first);
11385         }
11386     } else {
11387         currentMove = target;
11388     }
11389     
11390     if (gameMode == EditGame || gameMode == EndOfGame) {
11391         whiteTimeRemaining = timeRemaining[0][currentMove];
11392         blackTimeRemaining = timeRemaining[1][currentMove];
11393     }
11394     DisplayBothClocks();
11395     DisplayMove(currentMove - 1);
11396     DrawPosition(FALSE, boards[currentMove]);
11397     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11398     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11399         DisplayComment(currentMove - 1, commentList[currentMove]);
11400     }
11401 }
11402
11403
11404 void
11405 ForwardEvent()
11406 {
11407     if (gameMode == IcsExamining && !pausing) {
11408         SendToICS(ics_prefix);
11409         SendToICS("forward\n");
11410     } else {
11411         ForwardInner(currentMove + 1);
11412     }
11413 }
11414
11415 void
11416 ToEndEvent()
11417 {
11418     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11419         /* to optimze, we temporarily turn off analysis mode while we feed
11420          * the remaining moves to the engine. Otherwise we get analysis output
11421          * after each move.
11422          */ 
11423         if (first.analysisSupport) {
11424           SendToProgram("exit\nforce\n", &first);
11425           first.analyzing = FALSE;
11426         }
11427     }
11428         
11429     if (gameMode == IcsExamining && !pausing) {
11430         SendToICS(ics_prefix);
11431         SendToICS("forward 999999\n");
11432     } else {
11433         ForwardInner(forwardMostMove);
11434     }
11435
11436     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11437         /* we have fed all the moves, so reactivate analysis mode */
11438         SendToProgram("analyze\n", &first);
11439         first.analyzing = TRUE;
11440         /*first.maybeThinking = TRUE;*/
11441         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11442     }
11443 }
11444
11445 void
11446 BackwardInner(target)
11447      int target;
11448 {
11449     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11450
11451     if (appData.debugMode)
11452         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11453                 target, currentMove, forwardMostMove);
11454
11455     if (gameMode == EditPosition) return;
11456     if (currentMove <= backwardMostMove) {
11457         ClearHighlights();
11458         DrawPosition(full_redraw, boards[currentMove]);
11459         return;
11460     }
11461     if (gameMode == PlayFromGameFile && !pausing)
11462       PauseEvent();
11463     
11464     if (moveList[target][0]) {
11465         int fromX, fromY, toX, toY;
11466         toX = moveList[target][2] - AAA;
11467         toY = moveList[target][3] - ONE;
11468         if (moveList[target][1] == '@') {
11469             if (appData.highlightLastMove) {
11470                 SetHighlights(-1, -1, toX, toY);
11471             }
11472         } else {
11473             fromX = moveList[target][0] - AAA;
11474             fromY = moveList[target][1] - ONE;
11475             if (target == currentMove - 1) {
11476                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11477             }
11478             if (appData.highlightLastMove) {
11479                 SetHighlights(fromX, fromY, toX, toY);
11480             }
11481         }
11482     }
11483     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11484         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11485         while (currentMove > target) {
11486             SendToProgram("undo\n", &first);
11487             currentMove--;
11488         }
11489     } else {
11490         currentMove = target;
11491     }
11492     
11493     if (gameMode == EditGame || gameMode == EndOfGame) {
11494         whiteTimeRemaining = timeRemaining[0][currentMove];
11495         blackTimeRemaining = timeRemaining[1][currentMove];
11496     }
11497     DisplayBothClocks();
11498     DisplayMove(currentMove - 1);
11499     DrawPosition(full_redraw, boards[currentMove]);
11500     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11501     // [HGM] PV info: routine tests if comment empty
11502     DisplayComment(currentMove - 1, commentList[currentMove]);
11503 }
11504
11505 void
11506 BackwardEvent()
11507 {
11508     if (gameMode == IcsExamining && !pausing) {
11509         SendToICS(ics_prefix);
11510         SendToICS("backward\n");
11511     } else {
11512         BackwardInner(currentMove - 1);
11513     }
11514 }
11515
11516 void
11517 ToStartEvent()
11518 {
11519     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11520         /* to optimze, we temporarily turn off analysis mode while we undo
11521          * all the moves. Otherwise we get analysis output after each undo.
11522          */ 
11523         if (first.analysisSupport) {
11524           SendToProgram("exit\nforce\n", &first);
11525           first.analyzing = FALSE;
11526         }
11527     }
11528
11529     if (gameMode == IcsExamining && !pausing) {
11530         SendToICS(ics_prefix);
11531         SendToICS("backward 999999\n");
11532     } else {
11533         BackwardInner(backwardMostMove);
11534     }
11535
11536     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11537         /* we have fed all the moves, so reactivate analysis mode */
11538         SendToProgram("analyze\n", &first);
11539         first.analyzing = TRUE;
11540         /*first.maybeThinking = TRUE;*/
11541         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11542     }
11543 }
11544
11545 void
11546 ToNrEvent(int to)
11547 {
11548   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11549   if (to >= forwardMostMove) to = forwardMostMove;
11550   if (to <= backwardMostMove) to = backwardMostMove;
11551   if (to < currentMove) {
11552     BackwardInner(to);
11553   } else {
11554     ForwardInner(to);
11555   }
11556 }
11557
11558 void
11559 RevertEvent()
11560 {
11561     if (gameMode != IcsExamining) {
11562         DisplayError(_("You are not examining a game"), 0);
11563         return;
11564     }
11565     if (pausing) {
11566         DisplayError(_("You can't revert while pausing"), 0);
11567         return;
11568     }
11569     SendToICS(ics_prefix);
11570     SendToICS("revert\n");
11571 }
11572
11573 void
11574 RetractMoveEvent()
11575 {
11576     switch (gameMode) {
11577       case MachinePlaysWhite:
11578       case MachinePlaysBlack:
11579         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11580             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11581             return;
11582         }
11583         if (forwardMostMove < 2) return;
11584         currentMove = forwardMostMove = forwardMostMove - 2;
11585         whiteTimeRemaining = timeRemaining[0][currentMove];
11586         blackTimeRemaining = timeRemaining[1][currentMove];
11587         DisplayBothClocks();
11588         DisplayMove(currentMove - 1);
11589         ClearHighlights();/*!! could figure this out*/
11590         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11591         SendToProgram("remove\n", &first);
11592         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11593         break;
11594
11595       case BeginningOfGame:
11596       default:
11597         break;
11598
11599       case IcsPlayingWhite:
11600       case IcsPlayingBlack:
11601         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11602             SendToICS(ics_prefix);
11603             SendToICS("takeback 2\n");
11604         } else {
11605             SendToICS(ics_prefix);
11606             SendToICS("takeback 1\n");
11607         }
11608         break;
11609     }
11610 }
11611
11612 void
11613 MoveNowEvent()
11614 {
11615     ChessProgramState *cps;
11616
11617     switch (gameMode) {
11618       case MachinePlaysWhite:
11619         if (!WhiteOnMove(forwardMostMove)) {
11620             DisplayError(_("It is your turn"), 0);
11621             return;
11622         }
11623         cps = &first;
11624         break;
11625       case MachinePlaysBlack:
11626         if (WhiteOnMove(forwardMostMove)) {
11627             DisplayError(_("It is your turn"), 0);
11628             return;
11629         }
11630         cps = &first;
11631         break;
11632       case TwoMachinesPlay:
11633         if (WhiteOnMove(forwardMostMove) ==
11634             (first.twoMachinesColor[0] == 'w')) {
11635             cps = &first;
11636         } else {
11637             cps = &second;
11638         }
11639         break;
11640       case BeginningOfGame:
11641       default:
11642         return;
11643     }
11644     SendToProgram("?\n", cps);
11645 }
11646
11647 void
11648 TruncateGameEvent()
11649 {
11650     EditGameEvent();
11651     if (gameMode != EditGame) return;
11652     TruncateGame();
11653 }
11654
11655 void
11656 TruncateGame()
11657 {
11658     if (forwardMostMove > currentMove) {
11659         if (gameInfo.resultDetails != NULL) {
11660             free(gameInfo.resultDetails);
11661             gameInfo.resultDetails = NULL;
11662             gameInfo.result = GameUnfinished;
11663         }
11664         forwardMostMove = currentMove;
11665         HistorySet(parseList, backwardMostMove, forwardMostMove,
11666                    currentMove-1);
11667     }
11668 }
11669
11670 void
11671 HintEvent()
11672 {
11673     if (appData.noChessProgram) return;
11674     switch (gameMode) {
11675       case MachinePlaysWhite:
11676         if (WhiteOnMove(forwardMostMove)) {
11677             DisplayError(_("Wait until your turn"), 0);
11678             return;
11679         }
11680         break;
11681       case BeginningOfGame:
11682       case MachinePlaysBlack:
11683         if (!WhiteOnMove(forwardMostMove)) {
11684             DisplayError(_("Wait until your turn"), 0);
11685             return;
11686         }
11687         break;
11688       default:
11689         DisplayError(_("No hint available"), 0);
11690         return;
11691     }
11692     SendToProgram("hint\n", &first);
11693     hintRequested = TRUE;
11694 }
11695
11696 void
11697 BookEvent()
11698 {
11699     if (appData.noChessProgram) return;
11700     switch (gameMode) {
11701       case MachinePlaysWhite:
11702         if (WhiteOnMove(forwardMostMove)) {
11703             DisplayError(_("Wait until your turn"), 0);
11704             return;
11705         }
11706         break;
11707       case BeginningOfGame:
11708       case MachinePlaysBlack:
11709         if (!WhiteOnMove(forwardMostMove)) {
11710             DisplayError(_("Wait until your turn"), 0);
11711             return;
11712         }
11713         break;
11714       case EditPosition:
11715         EditPositionDone();
11716         break;
11717       case TwoMachinesPlay:
11718         return;
11719       default:
11720         break;
11721     }
11722     SendToProgram("bk\n", &first);
11723     bookOutput[0] = NULLCHAR;
11724     bookRequested = TRUE;
11725 }
11726
11727 void
11728 AboutGameEvent()
11729 {
11730     char *tags = PGNTags(&gameInfo);
11731     TagsPopUp(tags, CmailMsg());
11732     free(tags);
11733 }
11734
11735 /* end button procedures */
11736
11737 void
11738 PrintPosition(fp, move)
11739      FILE *fp;
11740      int move;
11741 {
11742     int i, j;
11743     
11744     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11745         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11746             char c = PieceToChar(boards[move][i][j]);
11747             fputc(c == 'x' ? '.' : c, fp);
11748             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11749         }
11750     }
11751     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11752       fprintf(fp, "white to play\n");
11753     else
11754       fprintf(fp, "black to play\n");
11755 }
11756
11757 void
11758 PrintOpponents(fp)
11759      FILE *fp;
11760 {
11761     if (gameInfo.white != NULL) {
11762         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11763     } else {
11764         fprintf(fp, "\n");
11765     }
11766 }
11767
11768 /* Find last component of program's own name, using some heuristics */
11769 void
11770 TidyProgramName(prog, host, buf)
11771      char *prog, *host, buf[MSG_SIZ];
11772 {
11773     char *p, *q;
11774     int local = (strcmp(host, "localhost") == 0);
11775     while (!local && (p = strchr(prog, ';')) != NULL) {
11776         p++;
11777         while (*p == ' ') p++;
11778         prog = p;
11779     }
11780     if (*prog == '"' || *prog == '\'') {
11781         q = strchr(prog + 1, *prog);
11782     } else {
11783         q = strchr(prog, ' ');
11784     }
11785     if (q == NULL) q = prog + strlen(prog);
11786     p = q;
11787     while (p >= prog && *p != '/' && *p != '\\') p--;
11788     p++;
11789     if(p == prog && *p == '"') p++;
11790     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11791     memcpy(buf, p, q - p);
11792     buf[q - p] = NULLCHAR;
11793     if (!local) {
11794         strcat(buf, "@");
11795         strcat(buf, host);
11796     }
11797 }
11798
11799 char *
11800 TimeControlTagValue()
11801 {
11802     char buf[MSG_SIZ];
11803     if (!appData.clockMode) {
11804         strcpy(buf, "-");
11805     } else if (movesPerSession > 0) {
11806         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11807     } else if (timeIncrement == 0) {
11808         sprintf(buf, "%ld", timeControl/1000);
11809     } else {
11810         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11811     }
11812     return StrSave(buf);
11813 }
11814
11815 void
11816 SetGameInfo()
11817 {
11818     /* This routine is used only for certain modes */
11819     VariantClass v = gameInfo.variant;
11820     ClearGameInfo(&gameInfo);
11821     gameInfo.variant = v;
11822
11823     switch (gameMode) {
11824       case MachinePlaysWhite:
11825         gameInfo.event = StrSave( appData.pgnEventHeader );
11826         gameInfo.site = StrSave(HostName());
11827         gameInfo.date = PGNDate();
11828         gameInfo.round = StrSave("-");
11829         gameInfo.white = StrSave(first.tidy);
11830         gameInfo.black = StrSave(UserName());
11831         gameInfo.timeControl = TimeControlTagValue();
11832         break;
11833
11834       case MachinePlaysBlack:
11835         gameInfo.event = StrSave( appData.pgnEventHeader );
11836         gameInfo.site = StrSave(HostName());
11837         gameInfo.date = PGNDate();
11838         gameInfo.round = StrSave("-");
11839         gameInfo.white = StrSave(UserName());
11840         gameInfo.black = StrSave(first.tidy);
11841         gameInfo.timeControl = TimeControlTagValue();
11842         break;
11843
11844       case TwoMachinesPlay:
11845         gameInfo.event = StrSave( appData.pgnEventHeader );
11846         gameInfo.site = StrSave(HostName());
11847         gameInfo.date = PGNDate();
11848         if (matchGame > 0) {
11849             char buf[MSG_SIZ];
11850             sprintf(buf, "%d", matchGame);
11851             gameInfo.round = StrSave(buf);
11852         } else {
11853             gameInfo.round = StrSave("-");
11854         }
11855         if (first.twoMachinesColor[0] == 'w') {
11856             gameInfo.white = StrSave(first.tidy);
11857             gameInfo.black = StrSave(second.tidy);
11858         } else {
11859             gameInfo.white = StrSave(second.tidy);
11860             gameInfo.black = StrSave(first.tidy);
11861         }
11862         gameInfo.timeControl = TimeControlTagValue();
11863         break;
11864
11865       case EditGame:
11866         gameInfo.event = StrSave("Edited game");
11867         gameInfo.site = StrSave(HostName());
11868         gameInfo.date = PGNDate();
11869         gameInfo.round = StrSave("-");
11870         gameInfo.white = StrSave("-");
11871         gameInfo.black = StrSave("-");
11872         break;
11873
11874       case EditPosition:
11875         gameInfo.event = StrSave("Edited position");
11876         gameInfo.site = StrSave(HostName());
11877         gameInfo.date = PGNDate();
11878         gameInfo.round = StrSave("-");
11879         gameInfo.white = StrSave("-");
11880         gameInfo.black = StrSave("-");
11881         break;
11882
11883       case IcsPlayingWhite:
11884       case IcsPlayingBlack:
11885       case IcsObserving:
11886       case IcsExamining:
11887         break;
11888
11889       case PlayFromGameFile:
11890         gameInfo.event = StrSave("Game from non-PGN file");
11891         gameInfo.site = StrSave(HostName());
11892         gameInfo.date = PGNDate();
11893         gameInfo.round = StrSave("-");
11894         gameInfo.white = StrSave("?");
11895         gameInfo.black = StrSave("?");
11896         break;
11897
11898       default:
11899         break;
11900     }
11901 }
11902
11903 void
11904 ReplaceComment(index, text)
11905      int index;
11906      char *text;
11907 {
11908     int len;
11909
11910     while (*text == '\n') text++;
11911     len = strlen(text);
11912     while (len > 0 && text[len - 1] == '\n') len--;
11913
11914     if (commentList[index] != NULL)
11915       free(commentList[index]);
11916
11917     if (len == 0) {
11918         commentList[index] = NULL;
11919         return;
11920     }
11921     commentList[index] = (char *) malloc(len + 2);
11922     strncpy(commentList[index], text, len);
11923     commentList[index][len] = '\n';
11924     commentList[index][len + 1] = NULLCHAR;
11925 }
11926
11927 void
11928 CrushCRs(text)
11929      char *text;
11930 {
11931   char *p = text;
11932   char *q = text;
11933   char ch;
11934
11935   do {
11936     ch = *p++;
11937     if (ch == '\r') continue;
11938     *q++ = ch;
11939   } while (ch != '\0');
11940 }
11941
11942 void
11943 AppendComment(index, text)
11944      int index;
11945      char *text;
11946 {
11947     int oldlen, len;
11948     char *old;
11949
11950     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11951
11952     CrushCRs(text);
11953     while (*text == '\n') text++;
11954     len = strlen(text);
11955     while (len > 0 && text[len - 1] == '\n') len--;
11956
11957     if (len == 0) return;
11958
11959     if (commentList[index] != NULL) {
11960         old = commentList[index];
11961         oldlen = strlen(old);
11962         commentList[index] = (char *) malloc(oldlen + len + 2);
11963         strcpy(commentList[index], old);
11964         free(old);
11965         strncpy(&commentList[index][oldlen], text, len);
11966         commentList[index][oldlen + len] = '\n';
11967         commentList[index][oldlen + len + 1] = NULLCHAR;
11968     } else {
11969         commentList[index] = (char *) malloc(len + 2);
11970         strncpy(commentList[index], text, len);
11971         commentList[index][len] = '\n';
11972         commentList[index][len + 1] = NULLCHAR;
11973     }
11974 }
11975
11976 static char * FindStr( char * text, char * sub_text )
11977 {
11978     char * result = strstr( text, sub_text );
11979
11980     if( result != NULL ) {
11981         result += strlen( sub_text );
11982     }
11983
11984     return result;
11985 }
11986
11987 /* [AS] Try to extract PV info from PGN comment */
11988 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11989 char *GetInfoFromComment( int index, char * text )
11990 {
11991     char * sep = text;
11992
11993     if( text != NULL && index > 0 ) {
11994         int score = 0;
11995         int depth = 0;
11996         int time = -1, sec = 0, deci;
11997         char * s_eval = FindStr( text, "[%eval " );
11998         char * s_emt = FindStr( text, "[%emt " );
11999
12000         if( s_eval != NULL || s_emt != NULL ) {
12001             /* New style */
12002             char delim;
12003
12004             if( s_eval != NULL ) {
12005                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12006                     return text;
12007                 }
12008
12009                 if( delim != ']' ) {
12010                     return text;
12011                 }
12012             }
12013
12014             if( s_emt != NULL ) {
12015             }
12016         }
12017         else {
12018             /* We expect something like: [+|-]nnn.nn/dd */
12019             int score_lo = 0;
12020
12021             sep = strchr( text, '/' );
12022             if( sep == NULL || sep < (text+4) ) {
12023                 return text;
12024             }
12025
12026             time = -1; sec = -1; deci = -1;
12027             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12028                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12029                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12030                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12031                 return text;
12032             }
12033
12034             if( score_lo < 0 || score_lo >= 100 ) {
12035                 return text;
12036             }
12037
12038             if(sec >= 0) time = 600*time + 10*sec; else
12039             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12040
12041             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12042
12043             /* [HGM] PV time: now locate end of PV info */
12044             while( *++sep >= '0' && *sep <= '9'); // strip depth
12045             if(time >= 0)
12046             while( *++sep >= '0' && *sep <= '9'); // strip time
12047             if(sec >= 0)
12048             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12049             if(deci >= 0)
12050             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12051             while(*sep == ' ') sep++;
12052         }
12053
12054         if( depth <= 0 ) {
12055             return text;
12056         }
12057
12058         if( time < 0 ) {
12059             time = -1;
12060         }
12061
12062         pvInfoList[index-1].depth = depth;
12063         pvInfoList[index-1].score = score;
12064         pvInfoList[index-1].time  = 10*time; // centi-sec
12065     }
12066     return sep;
12067 }
12068
12069 void
12070 SendToProgram(message, cps)
12071      char *message;
12072      ChessProgramState *cps;
12073 {
12074     int count, outCount, error;
12075     char buf[MSG_SIZ];
12076
12077     if (cps->pr == NULL) return;
12078     Attention(cps);
12079     
12080     if (appData.debugMode) {
12081         TimeMark now;
12082         GetTimeMark(&now);
12083         fprintf(debugFP, "%ld >%-6s: %s", 
12084                 SubtractTimeMarks(&now, &programStartTime),
12085                 cps->which, message);
12086     }
12087     
12088     count = strlen(message);
12089     outCount = OutputToProcess(cps->pr, message, count, &error);
12090     if (outCount < count && !exiting 
12091                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12092         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12093         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12094             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12095                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12096                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12097             } else {
12098                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12099             }
12100             gameInfo.resultDetails = buf;
12101         }
12102         DisplayFatalError(buf, error, 1);
12103     }
12104 }
12105
12106 void
12107 ReceiveFromProgram(isr, closure, message, count, error)
12108      InputSourceRef isr;
12109      VOIDSTAR closure;
12110      char *message;
12111      int count;
12112      int error;
12113 {
12114     char *end_str;
12115     char buf[MSG_SIZ];
12116     ChessProgramState *cps = (ChessProgramState *)closure;
12117
12118     if (isr != cps->isr) return; /* Killed intentionally */
12119     if (count <= 0) {
12120         if (count == 0) {
12121             sprintf(buf,
12122                     _("Error: %s chess program (%s) exited unexpectedly"),
12123                     cps->which, cps->program);
12124         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12125                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12126                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12127                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12128                 } else {
12129                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12130                 }
12131                 gameInfo.resultDetails = buf;
12132             }
12133             RemoveInputSource(cps->isr);
12134             DisplayFatalError(buf, 0, 1);
12135         } else {
12136             sprintf(buf,
12137                     _("Error reading from %s chess program (%s)"),
12138                     cps->which, cps->program);
12139             RemoveInputSource(cps->isr);
12140
12141             /* [AS] Program is misbehaving badly... kill it */
12142             if( count == -2 ) {
12143                 DestroyChildProcess( cps->pr, 9 );
12144                 cps->pr = NoProc;
12145             }
12146
12147             DisplayFatalError(buf, error, 1);
12148         }
12149         return;
12150     }
12151     
12152     if ((end_str = strchr(message, '\r')) != NULL)
12153       *end_str = NULLCHAR;
12154     if ((end_str = strchr(message, '\n')) != NULL)
12155       *end_str = NULLCHAR;
12156     
12157     if (appData.debugMode) {
12158         TimeMark now; int print = 1;
12159         char *quote = ""; char c; int i;
12160
12161         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12162                 char start = message[0];
12163                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12164                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12165                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12166                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12167                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12168                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12169                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12170                         { quote = "# "; print = (appData.engineComments == 2); }
12171                 message[0] = start; // restore original message
12172         }
12173         if(print) {
12174                 GetTimeMark(&now);
12175                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12176                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12177                         quote,
12178                         message);
12179         }
12180     }
12181
12182     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12183     if (appData.icsEngineAnalyze) {
12184         if (strstr(message, "whisper") != NULL ||
12185              strstr(message, "kibitz") != NULL || 
12186             strstr(message, "tellics") != NULL) return;
12187     }
12188
12189     HandleMachineMove(message, cps);
12190 }
12191
12192
12193 void
12194 SendTimeControl(cps, mps, tc, inc, sd, st)
12195      ChessProgramState *cps;
12196      int mps, inc, sd, st;
12197      long tc;
12198 {
12199     char buf[MSG_SIZ];
12200     int seconds;
12201
12202     if( timeControl_2 > 0 ) {
12203         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12204             tc = timeControl_2;
12205         }
12206     }
12207     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12208     inc /= cps->timeOdds;
12209     st  /= cps->timeOdds;
12210
12211     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12212
12213     if (st > 0) {
12214       /* Set exact time per move, normally using st command */
12215       if (cps->stKludge) {
12216         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12217         seconds = st % 60;
12218         if (seconds == 0) {
12219           sprintf(buf, "level 1 %d\n", st/60);
12220         } else {
12221           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12222         }
12223       } else {
12224         sprintf(buf, "st %d\n", st);
12225       }
12226     } else {
12227       /* Set conventional or incremental time control, using level command */
12228       if (seconds == 0) {
12229         /* Note old gnuchess bug -- minutes:seconds used to not work.
12230            Fixed in later versions, but still avoid :seconds
12231            when seconds is 0. */
12232         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12233       } else {
12234         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12235                 seconds, inc/1000);
12236       }
12237     }
12238     SendToProgram(buf, cps);
12239
12240     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12241     /* Orthogonally, limit search to given depth */
12242     if (sd > 0) {
12243       if (cps->sdKludge) {
12244         sprintf(buf, "depth\n%d\n", sd);
12245       } else {
12246         sprintf(buf, "sd %d\n", sd);
12247       }
12248       SendToProgram(buf, cps);
12249     }
12250
12251     if(cps->nps > 0) { /* [HGM] nps */
12252         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12253         else {
12254                 sprintf(buf, "nps %d\n", cps->nps);
12255               SendToProgram(buf, cps);
12256         }
12257     }
12258 }
12259
12260 ChessProgramState *WhitePlayer()
12261 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12262 {
12263     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12264        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12265         return &second;
12266     return &first;
12267 }
12268
12269 void
12270 SendTimeRemaining(cps, machineWhite)
12271      ChessProgramState *cps;
12272      int /*boolean*/ machineWhite;
12273 {
12274     char message[MSG_SIZ];
12275     long time, otime;
12276
12277     /* Note: this routine must be called when the clocks are stopped
12278        or when they have *just* been set or switched; otherwise
12279        it will be off by the time since the current tick started.
12280     */
12281     if (machineWhite) {
12282         time = whiteTimeRemaining / 10;
12283         otime = blackTimeRemaining / 10;
12284     } else {
12285         time = blackTimeRemaining / 10;
12286         otime = whiteTimeRemaining / 10;
12287     }
12288     /* [HGM] translate opponent's time by time-odds factor */
12289     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12290     if (appData.debugMode) {
12291         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12292     }
12293
12294     if (time <= 0) time = 1;
12295     if (otime <= 0) otime = 1;
12296     
12297     sprintf(message, "time %ld\n", time);
12298     SendToProgram(message, cps);
12299
12300     sprintf(message, "otim %ld\n", otime);
12301     SendToProgram(message, cps);
12302 }
12303
12304 int
12305 BoolFeature(p, name, loc, cps)
12306      char **p;
12307      char *name;
12308      int *loc;
12309      ChessProgramState *cps;
12310 {
12311   char buf[MSG_SIZ];
12312   int len = strlen(name);
12313   int val;
12314   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12315     (*p) += len + 1;
12316     sscanf(*p, "%d", &val);
12317     *loc = (val != 0);
12318     while (**p && **p != ' ') (*p)++;
12319     sprintf(buf, "accepted %s\n", name);
12320     SendToProgram(buf, cps);
12321     return TRUE;
12322   }
12323   return FALSE;
12324 }
12325
12326 int
12327 IntFeature(p, name, loc, cps)
12328      char **p;
12329      char *name;
12330      int *loc;
12331      ChessProgramState *cps;
12332 {
12333   char buf[MSG_SIZ];
12334   int len = strlen(name);
12335   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12336     (*p) += len + 1;
12337     sscanf(*p, "%d", loc);
12338     while (**p && **p != ' ') (*p)++;
12339     sprintf(buf, "accepted %s\n", name);
12340     SendToProgram(buf, cps);
12341     return TRUE;
12342   }
12343   return FALSE;
12344 }
12345
12346 int
12347 StringFeature(p, name, loc, cps)
12348      char **p;
12349      char *name;
12350      char loc[];
12351      ChessProgramState *cps;
12352 {
12353   char buf[MSG_SIZ];
12354   int len = strlen(name);
12355   if (strncmp((*p), name, len) == 0
12356       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12357     (*p) += len + 2;
12358     sscanf(*p, "%[^\"]", loc);
12359     while (**p && **p != '\"') (*p)++;
12360     if (**p == '\"') (*p)++;
12361     sprintf(buf, "accepted %s\n", name);
12362     SendToProgram(buf, cps);
12363     return TRUE;
12364   }
12365   return FALSE;
12366 }
12367
12368 int 
12369 ParseOption(Option *opt, ChessProgramState *cps)
12370 // [HGM] options: process the string that defines an engine option, and determine
12371 // name, type, default value, and allowed value range
12372 {
12373         char *p, *q, buf[MSG_SIZ];
12374         int n, min = (-1)<<31, max = 1<<31, def;
12375
12376         if(p = strstr(opt->name, " -spin ")) {
12377             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12378             if(max < min) max = min; // enforce consistency
12379             if(def < min) def = min;
12380             if(def > max) def = max;
12381             opt->value = def;
12382             opt->min = min;
12383             opt->max = max;
12384             opt->type = Spin;
12385         } else if(p = strstr(opt->name, " -string ")) {
12386             opt->textValue = p+9;
12387             opt->type = TextBox;
12388         } else if(p = strstr(opt->name, " -check ")) {
12389             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12390             opt->value = (def != 0);
12391             opt->type = CheckBox;
12392         } else if(p = strstr(opt->name, " -combo ")) {
12393             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12394             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12395             opt->value = n = 0;
12396             while(q = StrStr(q, " /// ")) {
12397                 n++; *q = 0;    // count choices, and null-terminate each of them
12398                 q += 5;
12399                 if(*q == '*') { // remember default, which is marked with * prefix
12400                     q++;
12401                     opt->value = n;
12402                 }
12403                 cps->comboList[cps->comboCnt++] = q;
12404             }
12405             cps->comboList[cps->comboCnt++] = NULL;
12406             opt->max = n + 1;
12407             opt->type = ComboBox;
12408         } else if(p = strstr(opt->name, " -button")) {
12409             opt->type = Button;
12410         } else if(p = strstr(opt->name, " -save")) {
12411             opt->type = SaveButton;
12412         } else return FALSE;
12413         *p = 0; // terminate option name
12414         // now look if the command-line options define a setting for this engine option.
12415         if(cps->optionSettings && cps->optionSettings[0])
12416             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12417         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12418                 sprintf(buf, "option %s", p);
12419                 if(p = strstr(buf, ",")) *p = 0;
12420                 strcat(buf, "\n");
12421                 SendToProgram(buf, cps);
12422         }
12423         return TRUE;
12424 }
12425
12426 void
12427 FeatureDone(cps, val)
12428      ChessProgramState* cps;
12429      int val;
12430 {
12431   DelayedEventCallback cb = GetDelayedEvent();
12432   if ((cb == InitBackEnd3 && cps == &first) ||
12433       (cb == TwoMachinesEventIfReady && cps == &second)) {
12434     CancelDelayedEvent();
12435     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12436   }
12437   cps->initDone = val;
12438 }
12439
12440 /* Parse feature command from engine */
12441 void
12442 ParseFeatures(args, cps)
12443      char* args;
12444      ChessProgramState *cps;  
12445 {
12446   char *p = args;
12447   char *q;
12448   int val;
12449   char buf[MSG_SIZ];
12450
12451   for (;;) {
12452     while (*p == ' ') p++;
12453     if (*p == NULLCHAR) return;
12454
12455     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12456     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12457     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12458     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12459     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12460     if (BoolFeature(&p, "reuse", &val, cps)) {
12461       /* Engine can disable reuse, but can't enable it if user said no */
12462       if (!val) cps->reuse = FALSE;
12463       continue;
12464     }
12465     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12466     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12467       if (gameMode == TwoMachinesPlay) {
12468         DisplayTwoMachinesTitle();
12469       } else {
12470         DisplayTitle("");
12471       }
12472       continue;
12473     }
12474     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12475     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12476     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12477     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12478     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12479     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12480     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12481     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12482     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12483     if (IntFeature(&p, "done", &val, cps)) {
12484       FeatureDone(cps, val);
12485       continue;
12486     }
12487     /* Added by Tord: */
12488     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12489     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12490     /* End of additions by Tord */
12491
12492     /* [HGM] added features: */
12493     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12494     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12495     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12496     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12497     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12498     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12499     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12500         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12501         if(cps->nrOptions >= MAX_OPTIONS) {
12502             cps->nrOptions--;
12503             sprintf(buf, "%s engine has too many options\n", cps->which);
12504             DisplayError(buf, 0);
12505         }
12506         continue;
12507     }
12508     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12509     /* End of additions by HGM */
12510
12511     /* unknown feature: complain and skip */
12512     q = p;
12513     while (*q && *q != '=') q++;
12514     sprintf(buf, "rejected %.*s\n", q-p, p);
12515     SendToProgram(buf, cps);
12516     p = q;
12517     if (*p == '=') {
12518       p++;
12519       if (*p == '\"') {
12520         p++;
12521         while (*p && *p != '\"') p++;
12522         if (*p == '\"') p++;
12523       } else {
12524         while (*p && *p != ' ') p++;
12525       }
12526     }
12527   }
12528
12529 }
12530
12531 void
12532 PeriodicUpdatesEvent(newState)
12533      int newState;
12534 {
12535     if (newState == appData.periodicUpdates)
12536       return;
12537
12538     appData.periodicUpdates=newState;
12539
12540     /* Display type changes, so update it now */
12541     DisplayAnalysis();
12542
12543     /* Get the ball rolling again... */
12544     if (newState) {
12545         AnalysisPeriodicEvent(1);
12546         StartAnalysisClock();
12547     }
12548 }
12549
12550 void
12551 PonderNextMoveEvent(newState)
12552      int newState;
12553 {
12554     if (newState == appData.ponderNextMove) return;
12555     if (gameMode == EditPosition) EditPositionDone();
12556     if (newState) {
12557         SendToProgram("hard\n", &first);
12558         if (gameMode == TwoMachinesPlay) {
12559             SendToProgram("hard\n", &second);
12560         }
12561     } else {
12562         SendToProgram("easy\n", &first);
12563         thinkOutput[0] = NULLCHAR;
12564         if (gameMode == TwoMachinesPlay) {
12565             SendToProgram("easy\n", &second);
12566         }
12567     }
12568     appData.ponderNextMove = newState;
12569 }
12570
12571 void
12572 NewSettingEvent(option, command, value)
12573      char *command;
12574      int option, value;
12575 {
12576     char buf[MSG_SIZ];
12577
12578     if (gameMode == EditPosition) EditPositionDone();
12579     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12580     SendToProgram(buf, &first);
12581     if (gameMode == TwoMachinesPlay) {
12582         SendToProgram(buf, &second);
12583     }
12584 }
12585
12586 void
12587 ShowThinkingEvent()
12588 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12589 {
12590     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12591     int newState = appData.showThinking
12592         // [HGM] thinking: other features now need thinking output as well
12593         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12594     
12595     if (oldState == newState) return;
12596     oldState = newState;
12597     if (gameMode == EditPosition) EditPositionDone();
12598     if (oldState) {
12599         SendToProgram("post\n", &first);
12600         if (gameMode == TwoMachinesPlay) {
12601             SendToProgram("post\n", &second);
12602         }
12603     } else {
12604         SendToProgram("nopost\n", &first);
12605         thinkOutput[0] = NULLCHAR;
12606         if (gameMode == TwoMachinesPlay) {
12607             SendToProgram("nopost\n", &second);
12608         }
12609     }
12610 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12611 }
12612
12613 void
12614 AskQuestionEvent(title, question, replyPrefix, which)
12615      char *title; char *question; char *replyPrefix; char *which;
12616 {
12617   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12618   if (pr == NoProc) return;
12619   AskQuestion(title, question, replyPrefix, pr);
12620 }
12621
12622 void
12623 DisplayMove(moveNumber)
12624      int moveNumber;
12625 {
12626     char message[MSG_SIZ];
12627     char res[MSG_SIZ];
12628     char cpThinkOutput[MSG_SIZ];
12629
12630     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12631     
12632     if (moveNumber == forwardMostMove - 1 || 
12633         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12634
12635         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12636
12637         if (strchr(cpThinkOutput, '\n')) {
12638             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12639         }
12640     } else {
12641         *cpThinkOutput = NULLCHAR;
12642     }
12643
12644     /* [AS] Hide thinking from human user */
12645     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12646         *cpThinkOutput = NULLCHAR;
12647         if( thinkOutput[0] != NULLCHAR ) {
12648             int i;
12649
12650             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12651                 cpThinkOutput[i] = '.';
12652             }
12653             cpThinkOutput[i] = NULLCHAR;
12654             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12655         }
12656     }
12657
12658     if (moveNumber == forwardMostMove - 1 &&
12659         gameInfo.resultDetails != NULL) {
12660         if (gameInfo.resultDetails[0] == NULLCHAR) {
12661             sprintf(res, " %s", PGNResult(gameInfo.result));
12662         } else {
12663             sprintf(res, " {%s} %s",
12664                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12665         }
12666     } else {
12667         res[0] = NULLCHAR;
12668     }
12669
12670     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12671         DisplayMessage(res, cpThinkOutput);
12672     } else {
12673         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12674                 WhiteOnMove(moveNumber) ? " " : ".. ",
12675                 parseList[moveNumber], res);
12676         DisplayMessage(message, cpThinkOutput);
12677     }
12678 }
12679
12680 void
12681 DisplayAnalysisText(text)
12682      char *text;
12683 {
12684     char buf[MSG_SIZ];
12685
12686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12687                || appData.icsEngineAnalyze) {
12688         sprintf(buf, "Analysis (%s)", first.tidy);
12689         AnalysisPopUp(buf, text);
12690     }
12691 }
12692
12693 static int
12694 only_one_move(str)
12695      char *str;
12696 {
12697     while (*str && isspace(*str)) ++str;
12698     while (*str && !isspace(*str)) ++str;
12699     if (!*str) return 1;
12700     while (*str && isspace(*str)) ++str;
12701     if (!*str) return 1;
12702     return 0;
12703 }
12704
12705 void
12706 DisplayAnalysis()
12707 {
12708     char buf[MSG_SIZ];
12709     char lst[MSG_SIZ / 2];
12710     double nps;
12711     static char *xtra[] = { "", " (--)", " (++)" };
12712     int h, m, s, cs;
12713   
12714     if (programStats.time == 0) {
12715         programStats.time = 1;
12716     }
12717   
12718     if (programStats.got_only_move) {
12719         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12720     } else {
12721         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12722
12723         nps = (u64ToDouble(programStats.nodes) /
12724              ((double)programStats.time /100.0));
12725
12726         cs = programStats.time % 100;
12727         s = programStats.time / 100;
12728         h = (s / (60*60));
12729         s = s - h*60*60;
12730         m = (s/60);
12731         s = s - m*60;
12732
12733         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12734           if (programStats.move_name[0] != NULLCHAR) {
12735             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12736                     programStats.depth,
12737                     programStats.nr_moves-programStats.moves_left,
12738                     programStats.nr_moves, programStats.move_name,
12739                     ((float)programStats.score)/100.0, lst,
12740                     only_one_move(lst)?
12741                     xtra[programStats.got_fail] : "",
12742                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12743           } else {
12744             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12745                     programStats.depth,
12746                     programStats.nr_moves-programStats.moves_left,
12747                     programStats.nr_moves, ((float)programStats.score)/100.0,
12748                     lst,
12749                     only_one_move(lst)?
12750                     xtra[programStats.got_fail] : "",
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12752           }
12753         } else {
12754             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12755                     programStats.depth,
12756                     ((float)programStats.score)/100.0,
12757                     lst,
12758                     only_one_move(lst)?
12759                     xtra[programStats.got_fail] : "",
12760                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12761         }
12762     }
12763     DisplayAnalysisText(buf);
12764 }
12765
12766 void
12767 DisplayComment(moveNumber, text)
12768      int moveNumber;
12769      char *text;
12770 {
12771     char title[MSG_SIZ];
12772     char buf[8000]; // comment can be long!
12773     int score, depth;
12774
12775     if( appData.autoDisplayComment ) {
12776         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12777             strcpy(title, "Comment");
12778         } else {
12779             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12780                     WhiteOnMove(moveNumber) ? " " : ".. ",
12781                     parseList[moveNumber]);
12782         }
12783         // [HGM] PV info: display PV info together with (or as) comment
12784         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12785             if(text == NULL) text = "";                                           
12786             score = pvInfoList[moveNumber].score;
12787             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12788                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12789             text = buf;
12790         }
12791     } else title[0] = 0;
12792
12793     if (text != NULL)
12794         CommentPopUp(title, text);
12795 }
12796
12797 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12798  * might be busy thinking or pondering.  It can be omitted if your
12799  * gnuchess is configured to stop thinking immediately on any user
12800  * input.  However, that gnuchess feature depends on the FIONREAD
12801  * ioctl, which does not work properly on some flavors of Unix.
12802  */
12803 void
12804 Attention(cps)
12805      ChessProgramState *cps;
12806 {
12807 #if ATTENTION
12808     if (!cps->useSigint) return;
12809     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12810     switch (gameMode) {
12811       case MachinePlaysWhite:
12812       case MachinePlaysBlack:
12813       case TwoMachinesPlay:
12814       case IcsPlayingWhite:
12815       case IcsPlayingBlack:
12816       case AnalyzeMode:
12817       case AnalyzeFile:
12818         /* Skip if we know it isn't thinking */
12819         if (!cps->maybeThinking) return;
12820         if (appData.debugMode)
12821           fprintf(debugFP, "Interrupting %s\n", cps->which);
12822         InterruptChildProcess(cps->pr);
12823         cps->maybeThinking = FALSE;
12824         break;
12825       default:
12826         break;
12827     }
12828 #endif /*ATTENTION*/
12829 }
12830
12831 int
12832 CheckFlags()
12833 {
12834     if (whiteTimeRemaining <= 0) {
12835         if (!whiteFlag) {
12836             whiteFlag = TRUE;
12837             if (appData.icsActive) {
12838                 if (appData.autoCallFlag &&
12839                     gameMode == IcsPlayingBlack && !blackFlag) {
12840                   SendToICS(ics_prefix);
12841                   SendToICS("flag\n");
12842                 }
12843             } else {
12844                 if (blackFlag) {
12845                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12846                 } else {
12847                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12848                     if (appData.autoCallFlag) {
12849                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12850                         return TRUE;
12851                     }
12852                 }
12853             }
12854         }
12855     }
12856     if (blackTimeRemaining <= 0) {
12857         if (!blackFlag) {
12858             blackFlag = TRUE;
12859             if (appData.icsActive) {
12860                 if (appData.autoCallFlag &&
12861                     gameMode == IcsPlayingWhite && !whiteFlag) {
12862                   SendToICS(ics_prefix);
12863                   SendToICS("flag\n");
12864                 }
12865             } else {
12866                 if (whiteFlag) {
12867                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12868                 } else {
12869                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12870                     if (appData.autoCallFlag) {
12871                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12872                         return TRUE;
12873                     }
12874                 }
12875             }
12876         }
12877     }
12878     return FALSE;
12879 }
12880
12881 void
12882 CheckTimeControl()
12883 {
12884     if (!appData.clockMode || appData.icsActive ||
12885         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12886
12887     /*
12888      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12889      */
12890     if ( !WhiteOnMove(forwardMostMove) )
12891         /* White made time control */
12892         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12893         /* [HGM] time odds: correct new time quota for time odds! */
12894                                             / WhitePlayer()->timeOdds;
12895       else
12896         /* Black made time control */
12897         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12898                                             / WhitePlayer()->other->timeOdds;
12899 }
12900
12901 void
12902 DisplayBothClocks()
12903 {
12904     int wom = gameMode == EditPosition ?
12905       !blackPlaysFirst : WhiteOnMove(currentMove);
12906     DisplayWhiteClock(whiteTimeRemaining, wom);
12907     DisplayBlackClock(blackTimeRemaining, !wom);
12908 }
12909
12910
12911 /* Timekeeping seems to be a portability nightmare.  I think everyone
12912    has ftime(), but I'm really not sure, so I'm including some ifdefs
12913    to use other calls if you don't.  Clocks will be less accurate if
12914    you have neither ftime nor gettimeofday.
12915 */
12916
12917 /* VS 2008 requires the #include outside of the function */
12918 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12919 #include <sys/timeb.h>
12920 #endif
12921
12922 /* Get the current time as a TimeMark */
12923 void
12924 GetTimeMark(tm)
12925      TimeMark *tm;
12926 {
12927 #if HAVE_GETTIMEOFDAY
12928
12929     struct timeval timeVal;
12930     struct timezone timeZone;
12931
12932     gettimeofday(&timeVal, &timeZone);
12933     tm->sec = (long) timeVal.tv_sec; 
12934     tm->ms = (int) (timeVal.tv_usec / 1000L);
12935
12936 #else /*!HAVE_GETTIMEOFDAY*/
12937 #if HAVE_FTIME
12938
12939 // include <sys/timeb.h> / moved to just above start of function
12940     struct timeb timeB;
12941
12942     ftime(&timeB);
12943     tm->sec = (long) timeB.time;
12944     tm->ms = (int) timeB.millitm;
12945
12946 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12947     tm->sec = (long) time(NULL);
12948     tm->ms = 0;
12949 #endif
12950 #endif
12951 }
12952
12953 /* Return the difference in milliseconds between two
12954    time marks.  We assume the difference will fit in a long!
12955 */
12956 long
12957 SubtractTimeMarks(tm2, tm1)
12958      TimeMark *tm2, *tm1;
12959 {
12960     return 1000L*(tm2->sec - tm1->sec) +
12961            (long) (tm2->ms - tm1->ms);
12962 }
12963
12964
12965 /*
12966  * Code to manage the game clocks.
12967  *
12968  * In tournament play, black starts the clock and then white makes a move.
12969  * We give the human user a slight advantage if he is playing white---the
12970  * clocks don't run until he makes his first move, so it takes zero time.
12971  * Also, we don't account for network lag, so we could get out of sync
12972  * with GNU Chess's clock -- but then, referees are always right.  
12973  */
12974
12975 static TimeMark tickStartTM;
12976 static long intendedTickLength;
12977
12978 long
12979 NextTickLength(timeRemaining)
12980      long timeRemaining;
12981 {
12982     long nominalTickLength, nextTickLength;
12983
12984     if (timeRemaining > 0L && timeRemaining <= 10000L)
12985       nominalTickLength = 100L;
12986     else
12987       nominalTickLength = 1000L;
12988     nextTickLength = timeRemaining % nominalTickLength;
12989     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12990
12991     return nextTickLength;
12992 }
12993
12994 /* Adjust clock one minute up or down */
12995 void
12996 AdjustClock(Boolean which, int dir)
12997 {
12998     if(which) blackTimeRemaining += 60000*dir;
12999     else      whiteTimeRemaining += 60000*dir;
13000     DisplayBothClocks();
13001 }
13002
13003 /* Stop clocks and reset to a fresh time control */
13004 void
13005 ResetClocks() 
13006 {
13007     (void) StopClockTimer();
13008     if (appData.icsActive) {
13009         whiteTimeRemaining = blackTimeRemaining = 0;
13010     } else { /* [HGM] correct new time quote for time odds */
13011         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13012         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13013     }
13014     if (whiteFlag || blackFlag) {
13015         DisplayTitle("");
13016         whiteFlag = blackFlag = FALSE;
13017     }
13018     DisplayBothClocks();
13019 }
13020
13021 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13022
13023 /* Decrement running clock by amount of time that has passed */
13024 void
13025 DecrementClocks()
13026 {
13027     long timeRemaining;
13028     long lastTickLength, fudge;
13029     TimeMark now;
13030
13031     if (!appData.clockMode) return;
13032     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13033         
13034     GetTimeMark(&now);
13035
13036     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13037
13038     /* Fudge if we woke up a little too soon */
13039     fudge = intendedTickLength - lastTickLength;
13040     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13041
13042     if (WhiteOnMove(forwardMostMove)) {
13043         if(whiteNPS >= 0) lastTickLength = 0;
13044         timeRemaining = whiteTimeRemaining -= lastTickLength;
13045         DisplayWhiteClock(whiteTimeRemaining - fudge,
13046                           WhiteOnMove(currentMove));
13047     } else {
13048         if(blackNPS >= 0) lastTickLength = 0;
13049         timeRemaining = blackTimeRemaining -= lastTickLength;
13050         DisplayBlackClock(blackTimeRemaining - fudge,
13051                           !WhiteOnMove(currentMove));
13052     }
13053
13054     if (CheckFlags()) return;
13055         
13056     tickStartTM = now;
13057     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13058     StartClockTimer(intendedTickLength);
13059
13060     /* if the time remaining has fallen below the alarm threshold, sound the
13061      * alarm. if the alarm has sounded and (due to a takeback or time control
13062      * with increment) the time remaining has increased to a level above the
13063      * threshold, reset the alarm so it can sound again. 
13064      */
13065     
13066     if (appData.icsActive && appData.icsAlarm) {
13067
13068         /* make sure we are dealing with the user's clock */
13069         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13070                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13071            )) return;
13072
13073         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13074             alarmSounded = FALSE;
13075         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13076             PlayAlarmSound();
13077             alarmSounded = TRUE;
13078         }
13079     }
13080 }
13081
13082
13083 /* A player has just moved, so stop the previously running
13084    clock and (if in clock mode) start the other one.
13085    We redisplay both clocks in case we're in ICS mode, because
13086    ICS gives us an update to both clocks after every move.
13087    Note that this routine is called *after* forwardMostMove
13088    is updated, so the last fractional tick must be subtracted
13089    from the color that is *not* on move now.
13090 */
13091 void
13092 SwitchClocks()
13093 {
13094     long lastTickLength;
13095     TimeMark now;
13096     int flagged = FALSE;
13097
13098     GetTimeMark(&now);
13099
13100     if (StopClockTimer() && appData.clockMode) {
13101         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13102         if (WhiteOnMove(forwardMostMove)) {
13103             if(blackNPS >= 0) lastTickLength = 0;
13104             blackTimeRemaining -= lastTickLength;
13105            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13106 //         if(pvInfoList[forwardMostMove-1].time == -1)
13107                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13108                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13109         } else {
13110            if(whiteNPS >= 0) lastTickLength = 0;
13111            whiteTimeRemaining -= lastTickLength;
13112            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13113 //         if(pvInfoList[forwardMostMove-1].time == -1)
13114                  pvInfoList[forwardMostMove-1].time = 
13115                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13116         }
13117         flagged = CheckFlags();
13118     }
13119     CheckTimeControl();
13120
13121     if (flagged || !appData.clockMode) return;
13122
13123     switch (gameMode) {
13124       case MachinePlaysBlack:
13125       case MachinePlaysWhite:
13126       case BeginningOfGame:
13127         if (pausing) return;
13128         break;
13129
13130       case EditGame:
13131       case PlayFromGameFile:
13132       case IcsExamining:
13133         return;
13134
13135       default:
13136         break;
13137     }
13138
13139     tickStartTM = now;
13140     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13141       whiteTimeRemaining : blackTimeRemaining);
13142     StartClockTimer(intendedTickLength);
13143 }
13144         
13145
13146 /* Stop both clocks */
13147 void
13148 StopClocks()
13149 {       
13150     long lastTickLength;
13151     TimeMark now;
13152
13153     if (!StopClockTimer()) return;
13154     if (!appData.clockMode) return;
13155
13156     GetTimeMark(&now);
13157
13158     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13159     if (WhiteOnMove(forwardMostMove)) {
13160         if(whiteNPS >= 0) lastTickLength = 0;
13161         whiteTimeRemaining -= lastTickLength;
13162         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13163     } else {
13164         if(blackNPS >= 0) lastTickLength = 0;
13165         blackTimeRemaining -= lastTickLength;
13166         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13167     }
13168     CheckFlags();
13169 }
13170         
13171 /* Start clock of player on move.  Time may have been reset, so
13172    if clock is already running, stop and restart it. */
13173 void
13174 StartClocks()
13175 {
13176     (void) StopClockTimer(); /* in case it was running already */
13177     DisplayBothClocks();
13178     if (CheckFlags()) return;
13179
13180     if (!appData.clockMode) return;
13181     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13182
13183     GetTimeMark(&tickStartTM);
13184     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13185       whiteTimeRemaining : blackTimeRemaining);
13186
13187    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13188     whiteNPS = blackNPS = -1; 
13189     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13190        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13191         whiteNPS = first.nps;
13192     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13193        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13194         blackNPS = first.nps;
13195     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13196         whiteNPS = second.nps;
13197     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13198         blackNPS = second.nps;
13199     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13200
13201     StartClockTimer(intendedTickLength);
13202 }
13203
13204 char *
13205 TimeString(ms)
13206      long ms;
13207 {
13208     long second, minute, hour, day;
13209     char *sign = "";
13210     static char buf[32];
13211     
13212     if (ms > 0 && ms <= 9900) {
13213       /* convert milliseconds to tenths, rounding up */
13214       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13215
13216       sprintf(buf, " %03.1f ", tenths/10.0);
13217       return buf;
13218     }
13219
13220     /* convert milliseconds to seconds, rounding up */
13221     /* use floating point to avoid strangeness of integer division
13222        with negative dividends on many machines */
13223     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13224
13225     if (second < 0) {
13226         sign = "-";
13227         second = -second;
13228     }
13229     
13230     day = second / (60 * 60 * 24);
13231     second = second % (60 * 60 * 24);
13232     hour = second / (60 * 60);
13233     second = second % (60 * 60);
13234     minute = second / 60;
13235     second = second % 60;
13236     
13237     if (day > 0)
13238       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13239               sign, day, hour, minute, second);
13240     else if (hour > 0)
13241       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13242     else
13243       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13244     
13245     return buf;
13246 }
13247
13248
13249 /*
13250  * This is necessary because some C libraries aren't ANSI C compliant yet.
13251  */
13252 char *
13253 StrStr(string, match)
13254      char *string, *match;
13255 {
13256     int i, length;
13257     
13258     length = strlen(match);
13259     
13260     for (i = strlen(string) - length; i >= 0; i--, string++)
13261       if (!strncmp(match, string, length))
13262         return string;
13263     
13264     return NULL;
13265 }
13266
13267 char *
13268 StrCaseStr(string, match)
13269      char *string, *match;
13270 {
13271     int i, j, length;
13272     
13273     length = strlen(match);
13274     
13275     for (i = strlen(string) - length; i >= 0; i--, string++) {
13276         for (j = 0; j < length; j++) {
13277             if (ToLower(match[j]) != ToLower(string[j]))
13278               break;
13279         }
13280         if (j == length) return string;
13281     }
13282
13283     return NULL;
13284 }
13285
13286 #ifndef _amigados
13287 int
13288 StrCaseCmp(s1, s2)
13289      char *s1, *s2;
13290 {
13291     char c1, c2;
13292     
13293     for (;;) {
13294         c1 = ToLower(*s1++);
13295         c2 = ToLower(*s2++);
13296         if (c1 > c2) return 1;
13297         if (c1 < c2) return -1;
13298         if (c1 == NULLCHAR) return 0;
13299     }
13300 }
13301
13302
13303 int
13304 ToLower(c)
13305      int c;
13306 {
13307     return isupper(c) ? tolower(c) : c;
13308 }
13309
13310
13311 int
13312 ToUpper(c)
13313      int c;
13314 {
13315     return islower(c) ? toupper(c) : c;
13316 }
13317 #endif /* !_amigados    */
13318
13319 char *
13320 StrSave(s)
13321      char *s;
13322 {
13323     char *ret;
13324
13325     if ((ret = (char *) malloc(strlen(s) + 1))) {
13326         strcpy(ret, s);
13327     }
13328     return ret;
13329 }
13330
13331 char *
13332 StrSavePtr(s, savePtr)
13333      char *s, **savePtr;
13334 {
13335     if (*savePtr) {
13336         free(*savePtr);
13337     }
13338     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13339         strcpy(*savePtr, s);
13340     }
13341     return(*savePtr);
13342 }
13343
13344 char *
13345 PGNDate()
13346 {
13347     time_t clock;
13348     struct tm *tm;
13349     char buf[MSG_SIZ];
13350
13351     clock = time((time_t *)NULL);
13352     tm = localtime(&clock);
13353     sprintf(buf, "%04d.%02d.%02d",
13354             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13355     return StrSave(buf);
13356 }
13357
13358
13359 char *
13360 PositionToFEN(move, overrideCastling)
13361      int move;
13362      char *overrideCastling;
13363 {
13364     int i, j, fromX, fromY, toX, toY;
13365     int whiteToPlay;
13366     char buf[128];
13367     char *p, *q;
13368     int emptycount;
13369     ChessSquare piece;
13370
13371     whiteToPlay = (gameMode == EditPosition) ?
13372       !blackPlaysFirst : (move % 2 == 0);
13373     p = buf;
13374
13375     /* Piece placement data */
13376     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13377         emptycount = 0;
13378         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13379             if (boards[move][i][j] == EmptySquare) {
13380                 emptycount++;
13381             } else { ChessSquare piece = boards[move][i][j];
13382                 if (emptycount > 0) {
13383                     if(emptycount<10) /* [HGM] can be >= 10 */
13384                         *p++ = '0' + emptycount;
13385                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13386                     emptycount = 0;
13387                 }
13388                 if(PieceToChar(piece) == '+') {
13389                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13390                     *p++ = '+';
13391                     piece = (ChessSquare)(DEMOTED piece);
13392                 } 
13393                 *p++ = PieceToChar(piece);
13394                 if(p[-1] == '~') {
13395                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13396                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13397                     *p++ = '~';
13398                 }
13399             }
13400         }
13401         if (emptycount > 0) {
13402             if(emptycount<10) /* [HGM] can be >= 10 */
13403                 *p++ = '0' + emptycount;
13404             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13405             emptycount = 0;
13406         }
13407         *p++ = '/';
13408     }
13409     *(p - 1) = ' ';
13410
13411     /* [HGM] print Crazyhouse or Shogi holdings */
13412     if( gameInfo.holdingsWidth ) {
13413         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13414         q = p;
13415         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13416             piece = boards[move][i][BOARD_WIDTH-1];
13417             if( piece != EmptySquare )
13418               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13419                   *p++ = PieceToChar(piece);
13420         }
13421         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13422             piece = boards[move][BOARD_HEIGHT-i-1][0];
13423             if( piece != EmptySquare )
13424               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13425                   *p++ = PieceToChar(piece);
13426         }
13427
13428         if( q == p ) *p++ = '-';
13429         *p++ = ']';
13430         *p++ = ' ';
13431     }
13432
13433     /* Active color */
13434     *p++ = whiteToPlay ? 'w' : 'b';
13435     *p++ = ' ';
13436
13437   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13438     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13439   } else {
13440   if(nrCastlingRights) {
13441      q = p;
13442      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13443        /* [HGM] write directly from rights */
13444            if(castlingRights[move][2] >= 0 &&
13445               castlingRights[move][0] >= 0   )
13446                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13447            if(castlingRights[move][2] >= 0 &&
13448               castlingRights[move][1] >= 0   )
13449                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13450            if(castlingRights[move][5] >= 0 &&
13451               castlingRights[move][3] >= 0   )
13452                 *p++ = castlingRights[move][3] + AAA;
13453            if(castlingRights[move][5] >= 0 &&
13454               castlingRights[move][4] >= 0   )
13455                 *p++ = castlingRights[move][4] + AAA;
13456      } else {
13457
13458         /* [HGM] write true castling rights */
13459         if( nrCastlingRights == 6 ) {
13460             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13461                castlingRights[move][2] >= 0  ) *p++ = 'K';
13462             if(castlingRights[move][1] == BOARD_LEFT &&
13463                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13464             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13465                castlingRights[move][5] >= 0  ) *p++ = 'k';
13466             if(castlingRights[move][4] == BOARD_LEFT &&
13467                castlingRights[move][5] >= 0  ) *p++ = 'q';
13468         }
13469      }
13470      if (q == p) *p++ = '-'; /* No castling rights */
13471      *p++ = ' ';
13472   }
13473
13474   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13475      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13476     /* En passant target square */
13477     if (move > backwardMostMove) {
13478         fromX = moveList[move - 1][0] - AAA;
13479         fromY = moveList[move - 1][1] - ONE;
13480         toX = moveList[move - 1][2] - AAA;
13481         toY = moveList[move - 1][3] - ONE;
13482         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13483             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13484             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13485             fromX == toX) {
13486             /* 2-square pawn move just happened */
13487             *p++ = toX + AAA;
13488             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13489         } else {
13490             *p++ = '-';
13491         }
13492     } else {
13493         *p++ = '-';
13494     }
13495     *p++ = ' ';
13496   }
13497   }
13498
13499     /* [HGM] find reversible plies */
13500     {   int i = 0, j=move;
13501
13502         if (appData.debugMode) { int k;
13503             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13504             for(k=backwardMostMove; k<=forwardMostMove; k++)
13505                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13506
13507         }
13508
13509         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13510         if( j == backwardMostMove ) i += initialRulePlies;
13511         sprintf(p, "%d ", i);
13512         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13513     }
13514     /* Fullmove number */
13515     sprintf(p, "%d", (move / 2) + 1);
13516     
13517     return StrSave(buf);
13518 }
13519
13520 Boolean
13521 ParseFEN(board, blackPlaysFirst, fen)
13522     Board board;
13523      int *blackPlaysFirst;
13524      char *fen;
13525 {
13526     int i, j;
13527     char *p;
13528     int emptycount;
13529     ChessSquare piece;
13530
13531     p = fen;
13532
13533     /* [HGM] by default clear Crazyhouse holdings, if present */
13534     if(gameInfo.holdingsWidth) {
13535        for(i=0; i<BOARD_HEIGHT; i++) {
13536            board[i][0]             = EmptySquare; /* black holdings */
13537            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13538            board[i][1]             = (ChessSquare) 0; /* black counts */
13539            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13540        }
13541     }
13542
13543     /* Piece placement data */
13544     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13545         j = 0;
13546         for (;;) {
13547             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13548                 if (*p == '/') p++;
13549                 emptycount = gameInfo.boardWidth - j;
13550                 while (emptycount--)
13551                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13552                 break;
13553 #if(BOARD_SIZE >= 10)
13554             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13555                 p++; emptycount=10;
13556                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13557                 while (emptycount--)
13558                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13559 #endif
13560             } else if (isdigit(*p)) {
13561                 emptycount = *p++ - '0';
13562                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13563                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13564                 while (emptycount--)
13565                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13566             } else if (*p == '+' || isalpha(*p)) {
13567                 if (j >= gameInfo.boardWidth) return FALSE;
13568                 if(*p=='+') {
13569                     piece = CharToPiece(*++p);
13570                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13571                     piece = (ChessSquare) (PROMOTED piece ); p++;
13572                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13573                 } else piece = CharToPiece(*p++);
13574
13575                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13576                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13577                     piece = (ChessSquare) (PROMOTED piece);
13578                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13579                     p++;
13580                 }
13581                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13582             } else {
13583                 return FALSE;
13584             }
13585         }
13586     }
13587     while (*p == '/' || *p == ' ') p++;
13588
13589     /* [HGM] look for Crazyhouse holdings here */
13590     while(*p==' ') p++;
13591     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13592         if(*p == '[') p++;
13593         if(*p == '-' ) *p++; /* empty holdings */ else {
13594             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13595             /* if we would allow FEN reading to set board size, we would   */
13596             /* have to add holdings and shift the board read so far here   */
13597             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13598                 *p++;
13599                 if((int) piece >= (int) BlackPawn ) {
13600                     i = (int)piece - (int)BlackPawn;
13601                     i = PieceToNumber((ChessSquare)i);
13602                     if( i >= gameInfo.holdingsSize ) return FALSE;
13603                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13604                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13605                 } else {
13606                     i = (int)piece - (int)WhitePawn;
13607                     i = PieceToNumber((ChessSquare)i);
13608                     if( i >= gameInfo.holdingsSize ) return FALSE;
13609                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13610                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13611                 }
13612             }
13613         }
13614         if(*p == ']') *p++;
13615     }
13616
13617     while(*p == ' ') p++;
13618
13619     /* Active color */
13620     switch (*p++) {
13621       case 'w':
13622         *blackPlaysFirst = FALSE;
13623         break;
13624       case 'b': 
13625         *blackPlaysFirst = TRUE;
13626         break;
13627       default:
13628         return FALSE;
13629     }
13630
13631     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13632     /* return the extra info in global variiables             */
13633
13634     /* set defaults in case FEN is incomplete */
13635     FENepStatus = EP_UNKNOWN;
13636     for(i=0; i<nrCastlingRights; i++ ) {
13637         FENcastlingRights[i] =
13638             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13639     }   /* assume possible unless obviously impossible */
13640     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13641     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13642     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13643     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13644     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13645     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13646     FENrulePlies = 0;
13647
13648     while(*p==' ') p++;
13649     if(nrCastlingRights) {
13650       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13651           /* castling indicator present, so default becomes no castlings */
13652           for(i=0; i<nrCastlingRights; i++ ) {
13653                  FENcastlingRights[i] = -1;
13654           }
13655       }
13656       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13657              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13658              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13659              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13660         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13661
13662         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13663             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13664             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13665         }
13666         switch(c) {
13667           case'K':
13668               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13669               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13670               FENcastlingRights[2] = whiteKingFile;
13671               break;
13672           case'Q':
13673               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13674               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13675               FENcastlingRights[2] = whiteKingFile;
13676               break;
13677           case'k':
13678               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13679               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13680               FENcastlingRights[5] = blackKingFile;
13681               break;
13682           case'q':
13683               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13684               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13685               FENcastlingRights[5] = blackKingFile;
13686           case '-':
13687               break;
13688           default: /* FRC castlings */
13689               if(c >= 'a') { /* black rights */
13690                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13691                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13692                   if(i == BOARD_RGHT) break;
13693                   FENcastlingRights[5] = i;
13694                   c -= AAA;
13695                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13696                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13697                   if(c > i)
13698                       FENcastlingRights[3] = c;
13699                   else
13700                       FENcastlingRights[4] = c;
13701               } else { /* white rights */
13702                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13703                     if(board[0][i] == WhiteKing) break;
13704                   if(i == BOARD_RGHT) break;
13705                   FENcastlingRights[2] = i;
13706                   c -= AAA - 'a' + 'A';
13707                   if(board[0][c] >= WhiteKing) break;
13708                   if(c > i)
13709                       FENcastlingRights[0] = c;
13710                   else
13711                       FENcastlingRights[1] = c;
13712               }
13713         }
13714       }
13715     if (appData.debugMode) {
13716         fprintf(debugFP, "FEN castling rights:");
13717         for(i=0; i<nrCastlingRights; i++)
13718         fprintf(debugFP, " %d", FENcastlingRights[i]);
13719         fprintf(debugFP, "\n");
13720     }
13721
13722       while(*p==' ') p++;
13723     }
13724
13725     /* read e.p. field in games that know e.p. capture */
13726     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13727        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13728       if(*p=='-') {
13729         p++; FENepStatus = EP_NONE;
13730       } else {
13731          char c = *p++ - AAA;
13732
13733          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13734          if(*p >= '0' && *p <='9') *p++;
13735          FENepStatus = c;
13736       }
13737     }
13738
13739
13740     if(sscanf(p, "%d", &i) == 1) {
13741         FENrulePlies = i; /* 50-move ply counter */
13742         /* (The move number is still ignored)    */
13743     }
13744
13745     return TRUE;
13746 }
13747       
13748 void
13749 EditPositionPasteFEN(char *fen)
13750 {
13751   if (fen != NULL) {
13752     Board initial_position;
13753
13754     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13755       DisplayError(_("Bad FEN position in clipboard"), 0);
13756       return ;
13757     } else {
13758       int savedBlackPlaysFirst = blackPlaysFirst;
13759       EditPositionEvent();
13760       blackPlaysFirst = savedBlackPlaysFirst;
13761       CopyBoard(boards[0], initial_position);
13762           /* [HGM] copy FEN attributes as well */
13763           {   int i;
13764               initialRulePlies = FENrulePlies;
13765               epStatus[0] = FENepStatus;
13766               for( i=0; i<nrCastlingRights; i++ )
13767                   castlingRights[0][i] = FENcastlingRights[i];
13768           }
13769       EditPositionDone();
13770       DisplayBothClocks();
13771       DrawPosition(FALSE, boards[currentMove]);
13772     }
13773   }
13774 }