new developer release
[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, 2010 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 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((int nr));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252
253 /* States for ics_getting_history */
254 #define H_FALSE 0
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
260
261 /* whosays values for GameEnds */
262 #define GE_ICS 0
263 #define GE_ENGINE 1
264 #define GE_PLAYER 2
265 #define GE_FILE 3
266 #define GE_XBOARD 4
267 #define GE_ENGINE1 5
268 #define GE_ENGINE2 6
269
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
272
273 /* Different types of move when calling RegisterMove */
274 #define CMAIL_MOVE   0
275 #define CMAIL_RESIGN 1
276 #define CMAIL_DRAW   2
277 #define CMAIL_ACCEPT 3
278
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
283
284 /* Telnet protocol constants */
285 #define TN_WILL 0373
286 #define TN_WONT 0374
287 #define TN_DO   0375
288 #define TN_DONT 0376
289 #define TN_IAC  0377
290 #define TN_ECHO 0001
291 #define TN_SGA  0003
292 #define TN_PORT 23
293
294 /* [AS] */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 {
297     assert( dst != NULL );
298     assert( src != NULL );
299     assert( count > 0 );
300
301     strncpy( dst, src, count );
302     dst[ count-1 ] = '\0';
303     return dst;
304 }
305
306 /* Some compiler can't cast u64 to double
307  * This function do the job for us:
308
309  * We use the highest bit for cast, this only
310  * works if the highest bit is not
311  * in use (This should not happen)
312  *
313  * We used this for all compiler
314  */
315 double
316 u64ToDouble(u64 value)
317 {
318   double r;
319   u64 tmp = value & u64Const(0x7fffffffffffffff);
320   r = (double)(s64)tmp;
321   if (value & u64Const(0x8000000000000000))
322        r +=  9.2233720368547758080e18; /* 2^63 */
323  return r;
324 }
325
326 /* Fake up flags for now, as we aren't keeping track of castling
327    availability yet. [HGM] Change of logic: the flag now only
328    indicates the type of castlings allowed by the rule of the game.
329    The actual rights themselves are maintained in the array
330    castlingRights, as part of the game history, and are not probed
331    by this function.
332  */
333 int
334 PosFlags(index)
335 {
336   int flags = F_ALL_CASTLE_OK;
337   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338   switch (gameInfo.variant) {
339   case VariantSuicide:
340     flags &= ~F_ALL_CASTLE_OK;
341   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342     flags |= F_IGNORE_CHECK;
343   case VariantLosers:
344     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345     break;
346   case VariantAtomic:
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348     break;
349   case VariantKriegspiel:
350     flags |= F_KRIEGSPIEL_CAPTURE;
351     break;
352   case VariantCapaRandom: 
353   case VariantFischeRandom:
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355   case VariantNoCastle:
356   case VariantShatranj:
357   case VariantCourier:
358   case VariantMakruk:
359     flags &= ~F_ALL_CASTLE_OK;
360     break;
361   default:
362     break;
363   }
364   return flags;
365 }
366
367 FILE *gameFileFP, *debugFP;
368
369 /* 
370     [AS] Note: sometimes, the sscanf() function is used to parse the input
371     into a fixed-size buffer. Because of this, we must be prepared to
372     receive strings as long as the size of the input buffer, which is currently
373     set to 4K for Windows and 8K for the rest.
374     So, we must either allocate sufficiently large buffers here, or
375     reduce the size of the input buffer in the input reading part.
376 */
377
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
381
382 ChessProgramState first, second;
383
384 /* premove variables */
385 int premoveToX = 0;
386 int premoveToY = 0;
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
390 int gotPremove = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
393
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
396
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
424 int movesPerSession;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
429 int matchGame = 0;
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
433
434 /* animateTraining preserves the state of appData.animate
435  * when Training mode is activated. This allows the
436  * response to be animated when appData.animate == TRUE and
437  * appData.animateDragging == TRUE.
438  */
439 Boolean animateTraining;
440
441 GameInfo gameInfo;
442
443 AppData appData;
444
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char  epStatus[MAX_MOVES];
448 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
449 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
450 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
451 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
452 int   initialRulePlies, FENrulePlies;
453 char  FENepStatus;
454 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int loadFlag = 0; 
456 int shuffleOpenings;
457 int mute; // mute all sounds
458
459 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
460     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
461         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
462     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
463         BlackKing, BlackBishop, BlackKnight, BlackRook }
464 };
465
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
468         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
470         BlackKing, BlackKing, BlackKnight, BlackRook }
471 };
472
473 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
474     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
475         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
476     { BlackRook, BlackMan, BlackBishop, BlackQueen,
477         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
478 };
479
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] white and black different armies! */
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
483     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
484         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
485 };
486
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
488     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
489         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
490     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
491         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
492 };
493
494 ChessSquare makrukArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
495     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
496         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
497     { BlackRook, BlackKnight, BlackMan, BlackFerz,
498         BlackKing, BlackMan, BlackKnight, BlackRook }
499 };
500
501
502 #if (BOARD_SIZE>=10)
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {
504     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
505         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
506     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
507         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
508 };
509
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
512         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
514         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 };
516
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
518     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
519         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
521         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
522 };
523
524 ChessSquare GreatArray[2][BOARD_SIZE] = {
525     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
526         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
527     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
528         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
529 };
530
531 ChessSquare JanusArray[2][BOARD_SIZE] = {
532     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
533         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
534     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
535         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
536 };
537
538 #ifdef GOTHIC
539 ChessSquare GothicArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
541         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
543         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
544 };
545 #else // !GOTHIC
546 #define GothicArray CapablancaArray
547 #endif // !GOTHIC
548
549 #ifdef FALCON
550 ChessSquare FalconArray[2][BOARD_SIZE] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
552         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
554         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
555 };
556 #else // !FALCON
557 #define FalconArray CapablancaArray
558 #endif // !FALCON
559
560 #else // !(BOARD_SIZE>=10)
561 #define XiangqiPosition FIDEArray
562 #define CapablancaArray FIDEArray
563 #define GothicArray FIDEArray
564 #define GreatArray FIDEArray
565 #endif // !(BOARD_SIZE>=10)
566
567 #if (BOARD_SIZE>=12)
568 ChessSquare CourierArray[2][BOARD_SIZE] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
570         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
572         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
573 };
574 #else // !(BOARD_SIZE>=12)
575 #define CourierArray CapablancaArray
576 #endif // !(BOARD_SIZE>=12)
577
578
579 Board initialPosition;
580
581
582 /* Convert str to a rating. Checks for special cases of "----",
583
584    "++++", etc. Also strips ()'s */
585 int
586 string_to_rating(str)
587   char *str;
588 {
589   while(*str && !isdigit(*str)) ++str;
590   if (!*str)
591     return 0;   /* One of the special "no rating" cases */
592   else
593     return atoi(str);
594 }
595
596 void
597 ClearProgramStats()
598 {
599     /* Init programStats */
600     programStats.movelist[0] = 0;
601     programStats.depth = 0;
602     programStats.nr_moves = 0;
603     programStats.moves_left = 0;
604     programStats.nodes = 0;
605     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
606     programStats.score = 0;
607     programStats.got_only_move = 0;
608     programStats.got_fail = 0;
609     programStats.line_is_book = 0;
610 }
611
612 void
613 InitBackEnd1()
614 {
615     int matched, min, sec;
616
617     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
618
619     GetTimeMark(&programStartTime);
620     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
621
622     ClearProgramStats();
623     programStats.ok_to_send = 1;
624     programStats.seen_stat = 0;
625
626     /*
627      * Initialize game list
628      */
629     ListNew(&gameList);
630
631
632     /*
633      * Internet chess server status
634      */
635     if (appData.icsActive) {
636         appData.matchMode = FALSE;
637         appData.matchGames = 0;
638 #if ZIPPY       
639         appData.noChessProgram = !appData.zippyPlay;
640 #else
641         appData.zippyPlay = FALSE;
642         appData.zippyTalk = FALSE;
643         appData.noChessProgram = TRUE;
644 #endif
645         if (*appData.icsHelper != NULLCHAR) {
646             appData.useTelnet = TRUE;
647             appData.telnetProgram = appData.icsHelper;
648         }
649     } else {
650         appData.zippyTalk = appData.zippyPlay = FALSE;
651     }
652
653     /* [AS] Initialize pv info list [HGM] and game state */
654     {
655         int i, j;
656
657         for( i=0; i<MAX_MOVES; i++ ) {
658             pvInfoList[i].depth = -1;
659             epStatus[i]=EP_NONE;
660             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
661         }
662     }
663
664     /*
665      * Parse timeControl resource
666      */
667     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
668                           appData.movesPerSession)) {
669         char buf[MSG_SIZ];
670         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
671         DisplayFatalError(buf, 0, 2);
672     }
673
674     /*
675      * Parse searchTime resource
676      */
677     if (*appData.searchTime != NULLCHAR) {
678         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
679         if (matched == 1) {
680             searchTime = min * 60;
681         } else if (matched == 2) {
682             searchTime = min * 60 + sec;
683         } else {
684             char buf[MSG_SIZ];
685             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
686             DisplayFatalError(buf, 0, 2);
687         }
688     }
689
690     /* [AS] Adjudication threshold */
691     adjudicateLossThreshold = appData.adjudicateLossThreshold;
692     
693     first.which = "first";
694     second.which = "second";
695     first.maybeThinking = second.maybeThinking = FALSE;
696     first.pr = second.pr = NoProc;
697     first.isr = second.isr = NULL;
698     first.sendTime = second.sendTime = 2;
699     first.sendDrawOffers = 1;
700     if (appData.firstPlaysBlack) {
701         first.twoMachinesColor = "black\n";
702         second.twoMachinesColor = "white\n";
703     } else {
704         first.twoMachinesColor = "white\n";
705         second.twoMachinesColor = "black\n";
706     }
707     first.program = appData.firstChessProgram;
708     second.program = appData.secondChessProgram;
709     first.host = appData.firstHost;
710     second.host = appData.secondHost;
711     first.dir = appData.firstDirectory;
712     second.dir = appData.secondDirectory;
713     first.other = &second;
714     second.other = &first;
715     first.initString = appData.initString;
716     second.initString = appData.secondInitString;
717     first.computerString = appData.firstComputerString;
718     second.computerString = appData.secondComputerString;
719     first.useSigint = second.useSigint = TRUE;
720     first.useSigterm = second.useSigterm = TRUE;
721     first.reuse = appData.reuseFirst;
722     second.reuse = appData.reuseSecond;
723     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
724     second.nps = appData.secondNPS;
725     first.useSetboard = second.useSetboard = FALSE;
726     first.useSAN = second.useSAN = FALSE;
727     first.usePing = second.usePing = FALSE;
728     first.lastPing = second.lastPing = 0;
729     first.lastPong = second.lastPong = 0;
730     first.usePlayother = second.usePlayother = FALSE;
731     first.useColors = second.useColors = TRUE;
732     first.useUsermove = second.useUsermove = FALSE;
733     first.sendICS = second.sendICS = FALSE;
734     first.sendName = second.sendName = appData.icsActive;
735     first.sdKludge = second.sdKludge = FALSE;
736     first.stKludge = second.stKludge = FALSE;
737     TidyProgramName(first.program, first.host, first.tidy);
738     TidyProgramName(second.program, second.host, second.tidy);
739     first.matchWins = second.matchWins = 0;
740     strcpy(first.variants, appData.variant);
741     strcpy(second.variants, appData.variant);
742     first.analysisSupport = second.analysisSupport = 2; /* detect */
743     first.analyzing = second.analyzing = FALSE;
744     first.initDone = second.initDone = FALSE;
745
746     /* New features added by Tord: */
747     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
748     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
749     /* End of new features added by Tord. */
750     first.fenOverride  = appData.fenOverride1;
751     second.fenOverride = appData.fenOverride2;
752
753     /* [HGM] time odds: set factor for each machine */
754     first.timeOdds  = appData.firstTimeOdds;
755     second.timeOdds = appData.secondTimeOdds;
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = first.timeOdds;
759             if(norm > second.timeOdds) norm = second.timeOdds;
760         }
761         first.timeOdds /= norm;
762         second.timeOdds /= norm;
763     }
764
765     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
766     first.accumulateTC = appData.firstAccumulateTC;
767     second.accumulateTC = appData.secondAccumulateTC;
768     first.maxNrOfSessions = second.maxNrOfSessions = 1;
769
770     /* [HGM] debug */
771     first.debug = second.debug = FALSE;
772     first.supportsNPS = second.supportsNPS = UNKNOWN;
773
774     /* [HGM] options */
775     first.optionSettings  = appData.firstOptions;
776     second.optionSettings = appData.secondOptions;
777
778     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
779     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
780     first.isUCI = appData.firstIsUCI; /* [AS] */
781     second.isUCI = appData.secondIsUCI; /* [AS] */
782     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
783     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
784
785     if (appData.firstProtocolVersion > PROTOVER ||
786         appData.firstProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.firstProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       first.protocolVersion = appData.firstProtocolVersion;
793     }
794
795     if (appData.secondProtocolVersion > PROTOVER ||
796         appData.secondProtocolVersion < 1) {
797       char buf[MSG_SIZ];
798       sprintf(buf, _("protocol version %d not supported"),
799               appData.secondProtocolVersion);
800       DisplayFatalError(buf, 0, 2);
801     } else {
802       second.protocolVersion = appData.secondProtocolVersion;
803     }
804
805     if (appData.icsActive) {
806         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
807     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
808         appData.clockMode = FALSE;
809         first.sendTime = second.sendTime = 0;
810     }
811     
812 #if ZIPPY
813     /* Override some settings from environment variables, for backward
814        compatibility.  Unfortunately it's not feasible to have the env
815        vars just set defaults, at least in xboard.  Ugh.
816     */
817     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
818       ZippyInit();
819     }
820 #endif
821     
822     if (appData.noChessProgram) {
823         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
824         sprintf(programVersion, "%s", PACKAGE_STRING);
825     } else {
826       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
827       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
828       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
829     }
830
831     if (!appData.icsActive) {
832       char buf[MSG_SIZ];
833       /* Check for variants that are supported only in ICS mode,
834          or not at all.  Some that are accepted here nevertheless
835          have bugs; see comments below.
836       */
837       VariantClass variant = StringToVariant(appData.variant);
838       switch (variant) {
839       case VariantBughouse:     /* need four players and two boards */
840       case VariantKriegspiel:   /* need to hide pieces and move details */
841       /* case VariantFischeRandom: (Fabien: moved below) */
842         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
843         DisplayFatalError(buf, 0, 2);
844         return;
845
846       case VariantUnknown:
847       case VariantLoadable:
848       case Variant29:
849       case Variant30:
850       case Variant31:
851       case Variant32:
852       case Variant33:
853       case Variant34:
854       case Variant35:
855       case Variant36:
856       default:
857         sprintf(buf, _("Unknown variant name %s"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
862       case VariantFairy:      /* [HGM] TestLegality definitely off! */
863       case VariantGothic:     /* [HGM] should work */
864       case VariantCapablanca: /* [HGM] should work */
865       case VariantCourier:    /* [HGM] initial forced moves not implemented */
866       case VariantShogi:      /* [HGM] drops not tested for legality */
867       case VariantKnightmate: /* [HGM] should work */
868       case VariantCylinder:   /* [HGM] untested */
869       case VariantFalcon:     /* [HGM] untested */
870       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
871                                  offboard interposition not understood */
872       case VariantNormal:     /* definitely works! */
873       case VariantWildCastle: /* pieces not automatically shuffled */
874       case VariantNoCastle:   /* pieces not automatically shuffled */
875       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
876       case VariantLosers:     /* should work except for win condition,
877                                  and doesn't know captures are mandatory */
878       case VariantSuicide:    /* should work except for win condition,
879                                  and doesn't know captures are mandatory */
880       case VariantGiveaway:   /* should work except for win condition,
881                                  and doesn't know captures are mandatory */
882       case VariantTwoKings:   /* should work */
883       case VariantAtomic:     /* should work except for win condition */
884       case Variant3Check:     /* should work except for win condition */
885       case VariantShatranj:   /* should work except for all win conditions */
886       case VariantMakruk:     /* should work except for daw countdown */
887       case VariantBerolina:   /* might work if TestLegality is off */
888       case VariantCapaRandom: /* should work */
889       case VariantJanus:      /* should work */
890       case VariantSuper:      /* experimental */
891       case VariantGreat:      /* experimental, requires legality testing to be off */
892         break;
893       }
894     }
895
896     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
897     InitEngineUCI( installDir, &second );
898 }
899
900 int NextIntegerFromString( char ** str, long * value )
901 {
902     int result = -1;
903     char * s = *str;
904
905     while( *s == ' ' || *s == '\t' ) {
906         s++;
907     }
908
909     *value = 0;
910
911     if( *s >= '0' && *s <= '9' ) {
912         while( *s >= '0' && *s <= '9' ) {
913             *value = *value * 10 + (*s - '0');
914             s++;
915         }
916
917         result = 0;
918     }
919
920     *str = s;
921
922     return result;
923 }
924
925 int NextTimeControlFromString( char ** str, long * value )
926 {
927     long temp;
928     int result = NextIntegerFromString( str, &temp );
929
930     if( result == 0 ) {
931         *value = temp * 60; /* Minutes */
932         if( **str == ':' ) {
933             (*str)++;
934             result = NextIntegerFromString( str, &temp );
935             *value += temp; /* Seconds */
936         }
937     }
938
939     return result;
940 }
941
942 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
943 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
944     int result = -1; long temp, temp2;
945
946     if(**str != '+') return -1; // old params remain in force!
947     (*str)++;
948     if( NextTimeControlFromString( str, &temp ) ) return -1;
949
950     if(**str != '/') {
951         /* time only: incremental or sudden-death time control */
952         if(**str == '+') { /* increment follows; read it */
953             (*str)++;
954             if(result = NextIntegerFromString( str, &temp2)) return -1;
955             *inc = temp2 * 1000;
956         } else *inc = 0;
957         *moves = 0; *tc = temp * 1000; 
958         return 0;
959     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
960
961     (*str)++; /* classical time control */
962     result = NextTimeControlFromString( str, &temp2);
963     if(result == 0) {
964         *moves = temp/60;
965         *tc    = temp2 * 1000;
966         *inc   = 0;
967     }
968     return result;
969 }
970
971 int GetTimeQuota(int movenr)
972 {   /* [HGM] get time to add from the multi-session time-control string */
973     int moves=1; /* kludge to force reading of first session */
974     long time, increment;
975     char *s = fullTimeControlString;
976
977     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
978     do {
979         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
980         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
981         if(movenr == -1) return time;    /* last move before new session     */
982         if(!moves) return increment;     /* current session is incremental   */
983         if(movenr >= 0) movenr -= moves; /* we already finished this session */
984     } while(movenr >= -1);               /* try again for next session       */
985
986     return 0; // no new time quota on this move
987 }
988
989 int
990 ParseTimeControl(tc, ti, mps)
991      char *tc;
992      int ti;
993      int mps;
994 {
995   long tc1;
996   long tc2;
997   char buf[MSG_SIZ];
998   
999   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1000   if(ti > 0) {
1001     if(mps)
1002       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1003     else sprintf(buf, "+%s+%d", tc, ti);
1004   } else {
1005     if(mps)
1006              sprintf(buf, "+%d/%s", mps, tc);
1007     else sprintf(buf, "+%s", tc);
1008   }
1009   fullTimeControlString = StrSave(buf);
1010   
1011   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1012     return FALSE;
1013   }
1014   
1015   if( *tc == '/' ) {
1016     /* Parse second time control */
1017     tc++;
1018     
1019     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1020       return FALSE;
1021     }
1022     
1023     if( tc2 == 0 ) {
1024       return FALSE;
1025     }
1026     
1027     timeControl_2 = tc2 * 1000;
1028   }
1029   else {
1030     timeControl_2 = 0;
1031   }
1032   
1033   if( tc1 == 0 ) {
1034     return FALSE;
1035   }
1036   
1037   timeControl = tc1 * 1000;
1038   
1039   if (ti >= 0) {
1040     timeIncrement = ti * 1000;  /* convert to ms */
1041     movesPerSession = 0;
1042   } else {
1043     timeIncrement = 0;
1044     movesPerSession = mps;
1045   }
1046   return TRUE;
1047 }
1048
1049 void
1050 InitBackEnd2()
1051 {
1052     if (appData.debugMode) {
1053         fprintf(debugFP, "%s\n", programVersion);
1054     }
1055
1056     set_cont_sequence(appData.wrapContSeq);
1057     if (appData.matchGames > 0) {
1058         appData.matchMode = TRUE;
1059     } else if (appData.matchMode) {
1060         appData.matchGames = 1;
1061     }
1062     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1063         appData.matchGames = appData.sameColorGames;
1064     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1065         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1066         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1067     }
1068     Reset(TRUE, FALSE);
1069     if (appData.noChessProgram || first.protocolVersion == 1) {
1070       InitBackEnd3();
1071     } else {
1072       /* kludge: allow timeout for initial "feature" commands */
1073       FreezeUI();
1074       DisplayMessage("", _("Starting chess program"));
1075       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1076     }
1077 }
1078
1079 void
1080 InitBackEnd3 P((void))
1081 {
1082     GameMode initialMode;
1083     char buf[MSG_SIZ];
1084     int err;
1085
1086     InitChessProgram(&first, startedFromSetupPosition);
1087
1088
1089     if (appData.icsActive) {
1090 #ifdef WIN32
1091         /* [DM] Make a console window if needed [HGM] merged ifs */
1092         ConsoleCreate(); 
1093 #endif
1094         err = establish();
1095         if (err != 0) {
1096             if (*appData.icsCommPort != NULLCHAR) {
1097                 sprintf(buf, _("Could not open comm port %s"),  
1098                         appData.icsCommPort);
1099             } else {
1100                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1101                         appData.icsHost, appData.icsPort);
1102             }
1103             DisplayFatalError(buf, err, 1);
1104             return;
1105         }
1106         SetICSMode();
1107         telnetISR =
1108           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1109         fromUserISR =
1110           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1111         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1112             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1113     } else if (appData.noChessProgram) {
1114         SetNCPMode();
1115     } else {
1116         SetGNUMode();
1117     }
1118
1119     if (*appData.cmailGameName != NULLCHAR) {
1120         SetCmailMode();
1121         OpenLoopback(&cmailPR);
1122         cmailISR =
1123           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1124     }
1125     
1126     ThawUI();
1127     DisplayMessage("", "");
1128     if (StrCaseCmp(appData.initialMode, "") == 0) {
1129       initialMode = BeginningOfGame;
1130     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1131       initialMode = TwoMachinesPlay;
1132     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1133       initialMode = AnalyzeFile; 
1134     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1135       initialMode = AnalyzeMode;
1136     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1137       initialMode = MachinePlaysWhite;
1138     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1139       initialMode = MachinePlaysBlack;
1140     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1141       initialMode = EditGame;
1142     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1143       initialMode = EditPosition;
1144     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1145       initialMode = Training;
1146     } else {
1147       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1148       DisplayFatalError(buf, 0, 2);
1149       return;
1150     }
1151
1152     if (appData.matchMode) {
1153         /* Set up machine vs. machine match */
1154         if (appData.noChessProgram) {
1155             DisplayFatalError(_("Can't have a match with no chess programs"),
1156                               0, 2);
1157             return;
1158         }
1159         matchMode = TRUE;
1160         matchGame = 1;
1161         if (*appData.loadGameFile != NULLCHAR) {
1162             int index = appData.loadGameIndex; // [HGM] autoinc
1163             if(index<0) lastIndex = index = 1;
1164             if (!LoadGameFromFile(appData.loadGameFile,
1165                                   index,
1166                                   appData.loadGameFile, FALSE)) {
1167                 DisplayFatalError(_("Bad game file"), 0, 1);
1168                 return;
1169             }
1170         } else if (*appData.loadPositionFile != NULLCHAR) {
1171             int index = appData.loadPositionIndex; // [HGM] autoinc
1172             if(index<0) lastIndex = index = 1;
1173             if (!LoadPositionFromFile(appData.loadPositionFile,
1174                                       index,
1175                                       appData.loadPositionFile)) {
1176                 DisplayFatalError(_("Bad position file"), 0, 1);
1177                 return;
1178             }
1179         }
1180         TwoMachinesEvent();
1181     } else if (*appData.cmailGameName != NULLCHAR) {
1182         /* Set up cmail mode */
1183         ReloadCmailMsgEvent(TRUE);
1184     } else {
1185         /* Set up other modes */
1186         if (initialMode == AnalyzeFile) {
1187           if (*appData.loadGameFile == NULLCHAR) {
1188             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1189             return;
1190           }
1191         }
1192         if (*appData.loadGameFile != NULLCHAR) {
1193             (void) LoadGameFromFile(appData.loadGameFile,
1194                                     appData.loadGameIndex,
1195                                     appData.loadGameFile, TRUE);
1196         } else if (*appData.loadPositionFile != NULLCHAR) {
1197             (void) LoadPositionFromFile(appData.loadPositionFile,
1198                                         appData.loadPositionIndex,
1199                                         appData.loadPositionFile);
1200             /* [HGM] try to make self-starting even after FEN load */
1201             /* to allow automatic setup of fairy variants with wtm */
1202             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1203                 gameMode = BeginningOfGame;
1204                 setboardSpoiledMachineBlack = 1;
1205             }
1206             /* [HGM] loadPos: make that every new game uses the setup */
1207             /* from file as long as we do not switch variant          */
1208             if(!blackPlaysFirst) { int i;
1209                 startedFromPositionFile = TRUE;
1210                 CopyBoard(filePosition, boards[0]);
1211                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1212             }
1213         }
1214         if (initialMode == AnalyzeMode) {
1215           if (appData.noChessProgram) {
1216             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1221             return;
1222           }
1223           AnalyzeModeEvent();
1224         } else if (initialMode == AnalyzeFile) {
1225           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1226           ShowThinkingEvent();
1227           AnalyzeFileEvent();
1228           AnalysisPeriodicEvent(1);
1229         } else if (initialMode == MachinePlaysWhite) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1232                               0, 2);
1233             return;
1234           }
1235           if (appData.icsActive) {
1236             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1237                               0, 2);
1238             return;
1239           }
1240           MachineWhiteEvent();
1241         } else if (initialMode == MachinePlaysBlack) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1244                               0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1249                               0, 2);
1250             return;
1251           }
1252           MachineBlackEvent();
1253         } else if (initialMode == TwoMachinesPlay) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           TwoMachinesEvent();
1265         } else if (initialMode == EditGame) {
1266           EditGameEvent();
1267         } else if (initialMode == EditPosition) {
1268           EditPositionEvent();
1269         } else if (initialMode == Training) {
1270           if (*appData.loadGameFile == NULLCHAR) {
1271             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1272             return;
1273           }
1274           TrainingEvent();
1275         }
1276     }
1277 }
1278
1279 /*
1280  * Establish will establish a contact to a remote host.port.
1281  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1282  *  used to talk to the host.
1283  * Returns 0 if okay, error code if not.
1284  */
1285 int
1286 establish()
1287 {
1288     char buf[MSG_SIZ];
1289
1290     if (*appData.icsCommPort != NULLCHAR) {
1291         /* Talk to the host through a serial comm port */
1292         return OpenCommPort(appData.icsCommPort, &icsPR);
1293
1294     } else if (*appData.gateway != NULLCHAR) {
1295         if (*appData.remoteShell == NULLCHAR) {
1296             /* Use the rcmd protocol to run telnet program on a gateway host */
1297             snprintf(buf, sizeof(buf), "%s %s %s",
1298                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1299             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300
1301         } else {
1302             /* Use the rsh program to run telnet program on a gateway host */
1303             if (*appData.remoteUser == NULLCHAR) {
1304                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1305                         appData.gateway, appData.telnetProgram,
1306                         appData.icsHost, appData.icsPort);
1307             } else {
1308                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1309                         appData.remoteShell, appData.gateway, 
1310                         appData.remoteUser, appData.telnetProgram,
1311                         appData.icsHost, appData.icsPort);
1312             }
1313             return StartChildProcess(buf, "", &icsPR);
1314
1315         }
1316     } else if (appData.useTelnet) {
1317         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318
1319     } else {
1320         /* TCP socket interface differs somewhat between
1321            Unix and NT; handle details in the front end.
1322            */
1323         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1324     }
1325 }
1326
1327 void
1328 show_bytes(fp, buf, count)
1329      FILE *fp;
1330      char *buf;
1331      int count;
1332 {
1333     while (count--) {
1334         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1335             fprintf(fp, "\\%03o", *buf & 0xff);
1336         } else {
1337             putc(*buf, fp);
1338         }
1339         buf++;
1340     }
1341     fflush(fp);
1342 }
1343
1344 /* Returns an errno value */
1345 int
1346 OutputMaybeTelnet(pr, message, count, outError)
1347      ProcRef pr;
1348      char *message;
1349      int count;
1350      int *outError;
1351 {
1352     char buf[8192], *p, *q, *buflim;
1353     int left, newcount, outcount;
1354
1355     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1356         *appData.gateway != NULLCHAR) {
1357         if (appData.debugMode) {
1358             fprintf(debugFP, ">ICS: ");
1359             show_bytes(debugFP, message, count);
1360             fprintf(debugFP, "\n");
1361         }
1362         return OutputToProcess(pr, message, count, outError);
1363     }
1364
1365     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1366     p = message;
1367     q = buf;
1368     left = count;
1369     newcount = 0;
1370     while (left) {
1371         if (q >= buflim) {
1372             if (appData.debugMode) {
1373                 fprintf(debugFP, ">ICS: ");
1374                 show_bytes(debugFP, buf, newcount);
1375                 fprintf(debugFP, "\n");
1376             }
1377             outcount = OutputToProcess(pr, buf, newcount, outError);
1378             if (outcount < newcount) return -1; /* to be sure */
1379             q = buf;
1380             newcount = 0;
1381         }
1382         if (*p == '\n') {
1383             *q++ = '\r';
1384             newcount++;
1385         } else if (((unsigned char) *p) == TN_IAC) {
1386             *q++ = (char) TN_IAC;
1387             newcount ++;
1388         }
1389         *q++ = *p++;
1390         newcount++;
1391         left--;
1392     }
1393     if (appData.debugMode) {
1394         fprintf(debugFP, ">ICS: ");
1395         show_bytes(debugFP, buf, newcount);
1396         fprintf(debugFP, "\n");
1397     }
1398     outcount = OutputToProcess(pr, buf, newcount, outError);
1399     if (outcount < newcount) return -1; /* to be sure */
1400     return count;
1401 }
1402
1403 void
1404 read_from_player(isr, closure, message, count, error)
1405      InputSourceRef isr;
1406      VOIDSTAR closure;
1407      char *message;
1408      int count;
1409      int error;
1410 {
1411     int outError, outCount;
1412     static int gotEof = 0;
1413
1414     /* Pass data read from player on to ICS */
1415     if (count > 0) {
1416         gotEof = 0;
1417         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1418         if (outCount < count) {
1419             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1420         }
1421     } else if (count < 0) {
1422         RemoveInputSource(isr);
1423         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1424     } else if (gotEof++ > 0) {
1425         RemoveInputSource(isr);
1426         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1427     }
1428 }
1429
1430 void
1431 KeepAlive()
1432 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1433     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1434     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1435     SendToICS("date\n");
1436     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1437 }
1438
1439 /* added routine for printf style output to ics */
1440 void ics_printf(char *format, ...)
1441 {
1442     char buffer[MSG_SIZ];
1443     va_list args;
1444
1445     va_start(args, format);
1446     vsnprintf(buffer, sizeof(buffer), format, args);
1447     buffer[sizeof(buffer)-1] = '\0';
1448     SendToICS(buffer);
1449     va_end(args);
1450 }
1451
1452 void
1453 SendToICS(s)
1454      char *s;
1455 {
1456     int count, outCount, outError;
1457
1458     if (icsPR == NULL) return;
1459
1460     count = strlen(s);
1461     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1462     if (outCount < count) {
1463         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1464     }
1465 }
1466
1467 /* This is used for sending logon scripts to the ICS. Sending
1468    without a delay causes problems when using timestamp on ICC
1469    (at least on my machine). */
1470 void
1471 SendToICSDelayed(s,msdelay)
1472      char *s;
1473      long msdelay;
1474 {
1475     int count, outCount, outError;
1476
1477     if (icsPR == NULL) return;
1478
1479     count = strlen(s);
1480     if (appData.debugMode) {
1481         fprintf(debugFP, ">ICS: ");
1482         show_bytes(debugFP, s, count);
1483         fprintf(debugFP, "\n");
1484     }
1485     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486                                       msdelay);
1487     if (outCount < count) {
1488         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489     }
1490 }
1491
1492
1493 /* Remove all highlighting escape sequences in s
1494    Also deletes any suffix starting with '(' 
1495    */
1496 char *
1497 StripHighlightAndTitle(s)
1498      char *s;
1499 {
1500     static char retbuf[MSG_SIZ];
1501     char *p = retbuf;
1502
1503     while (*s != NULLCHAR) {
1504         while (*s == '\033') {
1505             while (*s != NULLCHAR && !isalpha(*s)) s++;
1506             if (*s != NULLCHAR) s++;
1507         }
1508         while (*s != NULLCHAR && *s != '\033') {
1509             if (*s == '(' || *s == '[') {
1510                 *p = NULLCHAR;
1511                 return retbuf;
1512             }
1513             *p++ = *s++;
1514         }
1515     }
1516     *p = NULLCHAR;
1517     return retbuf;
1518 }
1519
1520 /* Remove all highlighting escape sequences in s */
1521 char *
1522 StripHighlight(s)
1523      char *s;
1524 {
1525     static char retbuf[MSG_SIZ];
1526     char *p = retbuf;
1527
1528     while (*s != NULLCHAR) {
1529         while (*s == '\033') {
1530             while (*s != NULLCHAR && !isalpha(*s)) s++;
1531             if (*s != NULLCHAR) s++;
1532         }
1533         while (*s != NULLCHAR && *s != '\033') {
1534             *p++ = *s++;
1535         }
1536     }
1537     *p = NULLCHAR;
1538     return retbuf;
1539 }
1540
1541 char *variantNames[] = VARIANT_NAMES;
1542 char *
1543 VariantName(v)
1544      VariantClass v;
1545 {
1546     return variantNames[v];
1547 }
1548
1549
1550 /* Identify a variant from the strings the chess servers use or the
1551    PGN Variant tag names we use. */
1552 VariantClass
1553 StringToVariant(e)
1554      char *e;
1555 {
1556     char *p;
1557     int wnum = -1;
1558     VariantClass v = VariantNormal;
1559     int i, found = FALSE;
1560     char buf[MSG_SIZ];
1561
1562     if (!e) return v;
1563
1564     /* [HGM] skip over optional board-size prefixes */
1565     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1566         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1567         while( *e++ != '_');
1568     }
1569
1570     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1571         v = VariantNormal;
1572         found = TRUE;
1573     } else
1574     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1575       if (StrCaseStr(e, variantNames[i])) {
1576         v = (VariantClass) i;
1577         found = TRUE;
1578         break;
1579       }
1580     }
1581
1582     if (!found) {
1583       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1584           || StrCaseStr(e, "wild/fr") 
1585           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1586         v = VariantFischeRandom;
1587       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1588                  (i = 1, p = StrCaseStr(e, "w"))) {
1589         p += i;
1590         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1591         if (isdigit(*p)) {
1592           wnum = atoi(p);
1593         } else {
1594           wnum = -1;
1595         }
1596         switch (wnum) {
1597         case 0: /* FICS only, actually */
1598         case 1:
1599           /* Castling legal even if K starts on d-file */
1600           v = VariantWildCastle;
1601           break;
1602         case 2:
1603         case 3:
1604         case 4:
1605           /* Castling illegal even if K & R happen to start in
1606              normal positions. */
1607           v = VariantNoCastle;
1608           break;
1609         case 5:
1610         case 7:
1611         case 8:
1612         case 10:
1613         case 11:
1614         case 12:
1615         case 13:
1616         case 14:
1617         case 15:
1618         case 18:
1619         case 19:
1620           /* Castling legal iff K & R start in normal positions */
1621           v = VariantNormal;
1622           break;
1623         case 6:
1624         case 20:
1625         case 21:
1626           /* Special wilds for position setup; unclear what to do here */
1627           v = VariantLoadable;
1628           break;
1629         case 9:
1630           /* Bizarre ICC game */
1631           v = VariantTwoKings;
1632           break;
1633         case 16:
1634           v = VariantKriegspiel;
1635           break;
1636         case 17:
1637           v = VariantLosers;
1638           break;
1639         case 22:
1640           v = VariantFischeRandom;
1641           break;
1642         case 23:
1643           v = VariantCrazyhouse;
1644           break;
1645         case 24:
1646           v = VariantBughouse;
1647           break;
1648         case 25:
1649           v = Variant3Check;
1650           break;
1651         case 26:
1652           /* Not quite the same as FICS suicide! */
1653           v = VariantGiveaway;
1654           break;
1655         case 27:
1656           v = VariantAtomic;
1657           break;
1658         case 28:
1659           v = VariantShatranj;
1660           break;
1661
1662         /* Temporary names for future ICC types.  The name *will* change in 
1663            the next xboard/WinBoard release after ICC defines it. */
1664         case 29:
1665           v = Variant29;
1666           break;
1667         case 30:
1668           v = Variant30;
1669           break;
1670         case 31:
1671           v = Variant31;
1672           break;
1673         case 32:
1674           v = Variant32;
1675           break;
1676         case 33:
1677           v = Variant33;
1678           break;
1679         case 34:
1680           v = Variant34;
1681           break;
1682         case 35:
1683           v = Variant35;
1684           break;
1685         case 36:
1686           v = Variant36;
1687           break;
1688         case 37:
1689           v = VariantShogi;
1690           break;
1691         case 38:
1692           v = VariantXiangqi;
1693           break;
1694         case 39:
1695           v = VariantCourier;
1696           break;
1697         case 40:
1698           v = VariantGothic;
1699           break;
1700         case 41:
1701           v = VariantCapablanca;
1702           break;
1703         case 42:
1704           v = VariantKnightmate;
1705           break;
1706         case 43:
1707           v = VariantFairy;
1708           break;
1709         case 44:
1710           v = VariantCylinder;
1711           break;
1712         case 45:
1713           v = VariantFalcon;
1714           break;
1715         case 46:
1716           v = VariantCapaRandom;
1717           break;
1718         case 47:
1719           v = VariantBerolina;
1720           break;
1721         case 48:
1722           v = VariantJanus;
1723           break;
1724         case 49:
1725           v = VariantSuper;
1726           break;
1727         case 50:
1728           v = VariantGreat;
1729           break;
1730         case -1:
1731           /* Found "wild" or "w" in the string but no number;
1732              must assume it's normal chess. */
1733           v = VariantNormal;
1734           break;
1735         default:
1736           sprintf(buf, _("Unknown wild type %d"), wnum);
1737           DisplayError(buf, 0);
1738           v = VariantUnknown;
1739           break;
1740         }
1741       }
1742     }
1743     if (appData.debugMode) {
1744       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1745               e, wnum, VariantName(v));
1746     }
1747     return v;
1748 }
1749
1750 static int leftover_start = 0, leftover_len = 0;
1751 char star_match[STAR_MATCH_N][MSG_SIZ];
1752
1753 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1754    advance *index beyond it, and set leftover_start to the new value of
1755    *index; else return FALSE.  If pattern contains the character '*', it
1756    matches any sequence of characters not containing '\r', '\n', or the
1757    character following the '*' (if any), and the matched sequence(s) are
1758    copied into star_match.
1759    */
1760 int
1761 looking_at(buf, index, pattern)
1762      char *buf;
1763      int *index;
1764      char *pattern;
1765 {
1766     char *bufp = &buf[*index], *patternp = pattern;
1767     int star_count = 0;
1768     char *matchp = star_match[0];
1769     
1770     for (;;) {
1771         if (*patternp == NULLCHAR) {
1772             *index = leftover_start = bufp - buf;
1773             *matchp = NULLCHAR;
1774             return TRUE;
1775         }
1776         if (*bufp == NULLCHAR) return FALSE;
1777         if (*patternp == '*') {
1778             if (*bufp == *(patternp + 1)) {
1779                 *matchp = NULLCHAR;
1780                 matchp = star_match[++star_count];
1781                 patternp += 2;
1782                 bufp++;
1783                 continue;
1784             } else if (*bufp == '\n' || *bufp == '\r') {
1785                 patternp++;
1786                 if (*patternp == NULLCHAR)
1787                   continue;
1788                 else
1789                   return FALSE;
1790             } else {
1791                 *matchp++ = *bufp++;
1792                 continue;
1793             }
1794         }
1795         if (*patternp != *bufp) return FALSE;
1796         patternp++;
1797         bufp++;
1798     }
1799 }
1800
1801 void
1802 SendToPlayer(data, length)
1803      char *data;
1804      int length;
1805 {
1806     int error, outCount;
1807     outCount = OutputToProcess(NoProc, data, length, &error);
1808     if (outCount < length) {
1809         DisplayFatalError(_("Error writing to display"), error, 1);
1810     }
1811 }
1812
1813 void
1814 PackHolding(packed, holding)
1815      char packed[];
1816      char *holding;
1817 {
1818     char *p = holding;
1819     char *q = packed;
1820     int runlength = 0;
1821     int curr = 9999;
1822     do {
1823         if (*p == curr) {
1824             runlength++;
1825         } else {
1826             switch (runlength) {
1827               case 0:
1828                 break;
1829               case 1:
1830                 *q++ = curr;
1831                 break;
1832               case 2:
1833                 *q++ = curr;
1834                 *q++ = curr;
1835                 break;
1836               default:
1837                 sprintf(q, "%d", runlength);
1838                 while (*q) q++;
1839                 *q++ = curr;
1840                 break;
1841             }
1842             runlength = 1;
1843             curr = *p;
1844         }
1845     } while (*p++);
1846     *q = NULLCHAR;
1847 }
1848
1849 /* Telnet protocol requests from the front end */
1850 void
1851 TelnetRequest(ddww, option)
1852      unsigned char ddww, option;
1853 {
1854     unsigned char msg[3];
1855     int outCount, outError;
1856
1857     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858
1859     if (appData.debugMode) {
1860         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861         switch (ddww) {
1862           case TN_DO:
1863             ddwwStr = "DO";
1864             break;
1865           case TN_DONT:
1866             ddwwStr = "DONT";
1867             break;
1868           case TN_WILL:
1869             ddwwStr = "WILL";
1870             break;
1871           case TN_WONT:
1872             ddwwStr = "WONT";
1873             break;
1874           default:
1875             ddwwStr = buf1;
1876             sprintf(buf1, "%d", ddww);
1877             break;
1878         }
1879         switch (option) {
1880           case TN_ECHO:
1881             optionStr = "ECHO";
1882             break;
1883           default:
1884             optionStr = buf2;
1885             sprintf(buf2, "%d", option);
1886             break;
1887         }
1888         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889     }
1890     msg[0] = TN_IAC;
1891     msg[1] = ddww;
1892     msg[2] = option;
1893     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894     if (outCount < 3) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 void
1900 DoEcho()
1901 {
1902     if (!appData.icsActive) return;
1903     TelnetRequest(TN_DO, TN_ECHO);
1904 }
1905
1906 void
1907 DontEcho()
1908 {
1909     if (!appData.icsActive) return;
1910     TelnetRequest(TN_DONT, TN_ECHO);
1911 }
1912
1913 void
1914 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 {
1916     /* put the holdings sent to us by the server on the board holdings area */
1917     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1918     char p;
1919     ChessSquare piece;
1920
1921     if(gameInfo.holdingsWidth < 2)  return;
1922     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1923         return; // prevent overwriting by pre-board holdings
1924
1925     if( (int)lowestPiece >= BlackPawn ) {
1926         holdingsColumn = 0;
1927         countsColumn = 1;
1928         holdingsStartRow = BOARD_HEIGHT-1;
1929         direction = -1;
1930     } else {
1931         holdingsColumn = BOARD_WIDTH-1;
1932         countsColumn = BOARD_WIDTH-2;
1933         holdingsStartRow = 0;
1934         direction = 1;
1935     }
1936
1937     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1938         board[i][holdingsColumn] = EmptySquare;
1939         board[i][countsColumn]   = (ChessSquare) 0;
1940     }
1941     while( (p=*holdings++) != NULLCHAR ) {
1942         piece = CharToPiece( ToUpper(p) );
1943         if(piece == EmptySquare) continue;
1944         /*j = (int) piece - (int) WhitePawn;*/
1945         j = PieceToNumber(piece);
1946         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1947         if(j < 0) continue;               /* should not happen */
1948         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1949         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1950         board[holdingsStartRow+j*direction][countsColumn]++;
1951     }
1952 }
1953
1954
1955 void
1956 VariantSwitch(Board board, VariantClass newVariant)
1957 {
1958    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1959    Board oldBoard;
1960
1961    startedFromPositionFile = FALSE;
1962    if(gameInfo.variant == newVariant) return;
1963
1964    /* [HGM] This routine is called each time an assignment is made to
1965     * gameInfo.variant during a game, to make sure the board sizes
1966     * are set to match the new variant. If that means adding or deleting
1967     * holdings, we shift the playing board accordingly
1968     * This kludge is needed because in ICS observe mode, we get boards
1969     * of an ongoing game without knowing the variant, and learn about the
1970     * latter only later. This can be because of the move list we requested,
1971     * in which case the game history is refilled from the beginning anyway,
1972     * but also when receiving holdings of a crazyhouse game. In the latter
1973     * case we want to add those holdings to the already received position.
1974     */
1975
1976    
1977    if (appData.debugMode) {
1978      fprintf(debugFP, "Switch board from %s to %s\n",
1979              VariantName(gameInfo.variant), VariantName(newVariant));
1980      setbuf(debugFP, NULL);
1981    }
1982    shuffleOpenings = 0;       /* [HGM] shuffle */
1983    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1984    switch(newVariant) 
1985      {
1986      case VariantShogi:
1987        newWidth = 9;  newHeight = 9;
1988        gameInfo.holdingsSize = 7;
1989      case VariantBughouse:
1990      case VariantCrazyhouse:
1991        newHoldingsWidth = 2; break;
1992      case VariantGreat:
1993        newWidth = 10;
1994      case VariantSuper:
1995        newHoldingsWidth = 2;
1996        gameInfo.holdingsSize = 8;
1997        break;
1998      case VariantGothic:
1999      case VariantCapablanca:
2000      case VariantCapaRandom:
2001        newWidth = 10;
2002      default:
2003        newHoldingsWidth = gameInfo.holdingsSize = 0;
2004      };
2005    
2006    if(newWidth  != gameInfo.boardWidth  ||
2007       newHeight != gameInfo.boardHeight ||
2008       newHoldingsWidth != gameInfo.holdingsWidth ) {
2009      
2010      /* shift position to new playing area, if needed */
2011      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2012        for(i=0; i<BOARD_HEIGHT; i++) 
2013          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2014            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015              board[i][j];
2016        for(i=0; i<newHeight; i++) {
2017          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2018          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019        }
2020      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2021        for(i=0; i<BOARD_HEIGHT; i++)
2022          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2023            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024              board[i][j];
2025      }
2026      gameInfo.boardWidth  = newWidth;
2027      gameInfo.boardHeight = newHeight;
2028      gameInfo.holdingsWidth = newHoldingsWidth;
2029      gameInfo.variant = newVariant;
2030      InitDrawingSizes(-2, 0);
2031    } else gameInfo.variant = newVariant;
2032    CopyBoard(oldBoard, board);   // remember correctly formatted board
2033      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2034    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2035 }
2036
2037 static int loggedOn = FALSE;
2038
2039 /*-- Game start info cache: --*/
2040 int gs_gamenum;
2041 char gs_kind[MSG_SIZ];
2042 static char player1Name[128] = "";
2043 static char player2Name[128] = "";
2044 static char cont_seq[] = "\n\\   ";
2045 static int player1Rating = -1;
2046 static int player2Rating = -1;
2047 /*----------------------------*/
2048
2049 ColorClass curColor = ColorNormal;
2050 int suppressKibitz = 0;
2051
2052 void
2053 read_from_ics(isr, closure, data, count, error)
2054      InputSourceRef isr;
2055      VOIDSTAR closure;
2056      char *data;
2057      int count;
2058      int error;
2059 {
2060 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2061 #define STARTED_NONE 0
2062 #define STARTED_MOVES 1
2063 #define STARTED_BOARD 2
2064 #define STARTED_OBSERVE 3
2065 #define STARTED_HOLDINGS 4
2066 #define STARTED_CHATTER 5
2067 #define STARTED_COMMENT 6
2068 #define STARTED_MOVES_NOHIDE 7
2069     
2070     static int started = STARTED_NONE;
2071     static char parse[20000];
2072     static int parse_pos = 0;
2073     static char buf[BUF_SIZE + 1];
2074     static int firstTime = TRUE, intfSet = FALSE;
2075     static ColorClass prevColor = ColorNormal;
2076     static int savingComment = FALSE;
2077     static int cmatch = 0; // continuation sequence match
2078     char *bp;
2079     char str[500];
2080     int i, oldi;
2081     int buf_len;
2082     int next_out;
2083     int tkind;
2084     int backup;    /* [DM] For zippy color lines */
2085     char *p;
2086     char talker[MSG_SIZ]; // [HGM] chat
2087     int channel;
2088
2089     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2090
2091     if (appData.debugMode) {
2092       if (!error) {
2093         fprintf(debugFP, "<ICS: ");
2094         show_bytes(debugFP, data, count);
2095         fprintf(debugFP, "\n");
2096       }
2097     }
2098
2099     if (appData.debugMode) { int f = forwardMostMove;
2100         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2101                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2102     }
2103     if (count > 0) {
2104         /* If last read ended with a partial line that we couldn't parse,
2105            prepend it to the new read and try again. */
2106         if (leftover_len > 0) {
2107             for (i=0; i<leftover_len; i++)
2108               buf[i] = buf[leftover_start + i];
2109         }
2110
2111     /* copy new characters into the buffer */
2112     bp = buf + leftover_len;
2113     buf_len=leftover_len;
2114     for (i=0; i<count; i++)
2115     {
2116         // ignore these
2117         if (data[i] == '\r')
2118             continue;
2119
2120         // join lines split by ICS?
2121         if (!appData.noJoin)
2122         {
2123             /*
2124                 Joining just consists of finding matches against the
2125                 continuation sequence, and discarding that sequence
2126                 if found instead of copying it.  So, until a match
2127                 fails, there's nothing to do since it might be the
2128                 complete sequence, and thus, something we don't want
2129                 copied.
2130             */
2131             if (data[i] == cont_seq[cmatch])
2132             {
2133                 cmatch++;
2134                 if (cmatch == strlen(cont_seq))
2135                 {
2136                     cmatch = 0; // complete match.  just reset the counter
2137
2138                     /*
2139                         it's possible for the ICS to not include the space
2140                         at the end of the last word, making our [correct]
2141                         join operation fuse two separate words.  the server
2142                         does this when the space occurs at the width setting.
2143                     */
2144                     if (!buf_len || buf[buf_len-1] != ' ')
2145                     {
2146                         *bp++ = ' ';
2147                         buf_len++;
2148                     }
2149                 }
2150                 continue;
2151             }
2152             else if (cmatch)
2153             {
2154                 /*
2155                     match failed, so we have to copy what matched before
2156                     falling through and copying this character.  In reality,
2157                     this will only ever be just the newline character, but
2158                     it doesn't hurt to be precise.
2159                 */
2160                 strncpy(bp, cont_seq, cmatch);
2161                 bp += cmatch;
2162                 buf_len += cmatch;
2163                 cmatch = 0;
2164             }
2165         }
2166
2167         // copy this char
2168         *bp++ = data[i];
2169         buf_len++;
2170     }
2171
2172         buf[buf_len] = NULLCHAR;
2173 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2174         next_out = 0;
2175         leftover_start = 0;
2176         
2177         i = 0;
2178         while (i < buf_len) {
2179             /* Deal with part of the TELNET option negotiation
2180                protocol.  We refuse to do anything beyond the
2181                defaults, except that we allow the WILL ECHO option,
2182                which ICS uses to turn off password echoing when we are
2183                directly connected to it.  We reject this option
2184                if localLineEditing mode is on (always on in xboard)
2185                and we are talking to port 23, which might be a real
2186                telnet server that will try to keep WILL ECHO on permanently.
2187              */
2188             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2189                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2190                 unsigned char option;
2191                 oldi = i;
2192                 switch ((unsigned char) buf[++i]) {
2193                   case TN_WILL:
2194                     if (appData.debugMode)
2195                       fprintf(debugFP, "\n<WILL ");
2196                     switch (option = (unsigned char) buf[++i]) {
2197                       case TN_ECHO:
2198                         if (appData.debugMode)
2199                           fprintf(debugFP, "ECHO ");
2200                         /* Reply only if this is a change, according
2201                            to the protocol rules. */
2202                         if (remoteEchoOption) break;
2203                         if (appData.localLineEditing &&
2204                             atoi(appData.icsPort) == TN_PORT) {
2205                             TelnetRequest(TN_DONT, TN_ECHO);
2206                         } else {
2207                             EchoOff();
2208                             TelnetRequest(TN_DO, TN_ECHO);
2209                             remoteEchoOption = TRUE;
2210                         }
2211                         break;
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", option);
2215                         /* Whatever this is, we don't want it. */
2216                         TelnetRequest(TN_DONT, option);
2217                         break;
2218                     }
2219                     break;
2220                   case TN_WONT:
2221                     if (appData.debugMode)
2222                       fprintf(debugFP, "\n<WONT ");
2223                     switch (option = (unsigned char) buf[++i]) {
2224                       case TN_ECHO:
2225                         if (appData.debugMode)
2226                           fprintf(debugFP, "ECHO ");
2227                         /* Reply only if this is a change, according
2228                            to the protocol rules. */
2229                         if (!remoteEchoOption) break;
2230                         EchoOn();
2231                         TelnetRequest(TN_DONT, TN_ECHO);
2232                         remoteEchoOption = FALSE;
2233                         break;
2234                       default:
2235                         if (appData.debugMode)
2236                           fprintf(debugFP, "%d ", (unsigned char) option);
2237                         /* Whatever this is, it must already be turned
2238                            off, because we never agree to turn on
2239                            anything non-default, so according to the
2240                            protocol rules, we don't reply. */
2241                         break;
2242                     }
2243                     break;
2244                   case TN_DO:
2245                     if (appData.debugMode)
2246                       fprintf(debugFP, "\n<DO ");
2247                     switch (option = (unsigned char) buf[++i]) {
2248                       default:
2249                         /* Whatever this is, we refuse to do it. */
2250                         if (appData.debugMode)
2251                           fprintf(debugFP, "%d ", option);
2252                         TelnetRequest(TN_WONT, option);
2253                         break;
2254                     }
2255                     break;
2256                   case TN_DONT:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<DONT ");
2259                     switch (option = (unsigned char) buf[++i]) {
2260                       default:
2261                         if (appData.debugMode)
2262                           fprintf(debugFP, "%d ", option);
2263                         /* Whatever this is, we are already not doing
2264                            it, because we never agree to do anything
2265                            non-default, so according to the protocol
2266                            rules, we don't reply. */
2267                         break;
2268                     }
2269                     break;
2270                   case TN_IAC:
2271                     if (appData.debugMode)
2272                       fprintf(debugFP, "\n<IAC ");
2273                     /* Doubled IAC; pass it through */
2274                     i--;
2275                     break;
2276                   default:
2277                     if (appData.debugMode)
2278                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2279                     /* Drop all other telnet commands on the floor */
2280                     break;
2281                 }
2282                 if (oldi > next_out)
2283                   SendToPlayer(&buf[next_out], oldi - next_out);
2284                 if (++i > next_out)
2285                   next_out = i;
2286                 continue;
2287             }
2288                 
2289             /* OK, this at least will *usually* work */
2290             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2291                 loggedOn = TRUE;
2292             }
2293             
2294             if (loggedOn && !intfSet) {
2295                 if (ics_type == ICS_ICC) {
2296                   sprintf(str,
2297                           "/set-quietly interface %s\n/set-quietly style 12\n",
2298                           programVersion);
2299                 } else if (ics_type == ICS_CHESSNET) {
2300                   sprintf(str, "/style 12\n");
2301                 } else {
2302                   strcpy(str, "alias $ @\n$set interface ");
2303                   strcat(str, programVersion);
2304                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2305 #ifdef WIN32
2306                   strcat(str, "$iset nohighlight 1\n");
2307 #endif
2308                   strcat(str, "$iset lock 1\n$style 12\n");
2309                 }
2310                 SendToICS(str);
2311                 NotifyFrontendLogin();
2312                 intfSet = TRUE;
2313             }
2314
2315             if (started == STARTED_COMMENT) {
2316                 /* Accumulate characters in comment */
2317                 parse[parse_pos++] = buf[i];
2318                 if (buf[i] == '\n') {
2319                     parse[parse_pos] = NULLCHAR;
2320                     if(chattingPartner>=0) {
2321                         char mess[MSG_SIZ];
2322                         sprintf(mess, "%s%s", talker, parse);
2323                         OutputChatMessage(chattingPartner, mess);
2324                         chattingPartner = -1;
2325                         next_out = i+1; // [HGM] suppress printing in ICS window
2326                     } else
2327                     if(!suppressKibitz) // [HGM] kibitz
2328                         AppendComment(forwardMostMove, StripHighlight(parse));
2329                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2330                         int nrDigit = 0, nrAlph = 0, j;
2331                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2332                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2333                         parse[parse_pos] = NULLCHAR;
2334                         // try to be smart: if it does not look like search info, it should go to
2335                         // ICS interaction window after all, not to engine-output window.
2336                         for(j=0; j<parse_pos; j++) { // count letters and digits
2337                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2338                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2339                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2340                         }
2341                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2342                             int depth=0; float score;
2343                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2344                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2345                                 pvInfoList[forwardMostMove-1].depth = depth;
2346                                 pvInfoList[forwardMostMove-1].score = 100*score;
2347                             }
2348                             OutputKibitz(suppressKibitz, parse);
2349                         } else {
2350                             char tmp[MSG_SIZ];
2351                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2352                             SendToPlayer(tmp, strlen(tmp));
2353                         }
2354                         next_out = i+1; // [HGM] suppress printing in ICS window
2355                     }
2356                     started = STARTED_NONE;
2357                 } else {
2358                     /* Don't match patterns against characters in comment */
2359                     i++;
2360                     continue;
2361                 }
2362             }
2363             if (started == STARTED_CHATTER) {
2364                 if (buf[i] != '\n') {
2365                     /* Don't match patterns against characters in chatter */
2366                     i++;
2367                     continue;
2368                 }
2369                 started = STARTED_NONE;
2370                 if(suppressKibitz) next_out = i+1;
2371             }
2372
2373             /* Kludge to deal with rcmd protocol */
2374             if (firstTime && looking_at(buf, &i, "\001*")) {
2375                 DisplayFatalError(&buf[1], 0, 1);
2376                 continue;
2377             } else {
2378                 firstTime = FALSE;
2379             }
2380
2381             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2382                 ics_type = ICS_ICC;
2383                 ics_prefix = "/";
2384                 if (appData.debugMode)
2385                   fprintf(debugFP, "ics_type %d\n", ics_type);
2386                 continue;
2387             }
2388             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2389                 ics_type = ICS_FICS;
2390                 ics_prefix = "$";
2391                 if (appData.debugMode)
2392                   fprintf(debugFP, "ics_type %d\n", ics_type);
2393                 continue;
2394             }
2395             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2396                 ics_type = ICS_CHESSNET;
2397                 ics_prefix = "/";
2398                 if (appData.debugMode)
2399                   fprintf(debugFP, "ics_type %d\n", ics_type);
2400                 continue;
2401             }
2402
2403             if (!loggedOn &&
2404                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2405                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2406                  looking_at(buf, &i, "will be \"*\""))) {
2407               strcpy(ics_handle, star_match[0]);
2408               continue;
2409             }
2410
2411             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2412               char buf[MSG_SIZ];
2413               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2414               DisplayIcsInteractionTitle(buf);
2415               have_set_title = TRUE;
2416             }
2417
2418             /* skip finger notes */
2419             if (started == STARTED_NONE &&
2420                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2421                  (buf[i] == '1' && buf[i+1] == '0')) &&
2422                 buf[i+2] == ':' && buf[i+3] == ' ') {
2423               started = STARTED_CHATTER;
2424               i += 3;
2425               continue;
2426             }
2427
2428             /* skip formula vars */
2429             if (started == STARTED_NONE &&
2430                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2431               started = STARTED_CHATTER;
2432               i += 3;
2433               continue;
2434             }
2435
2436             oldi = i;
2437             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2438             if (appData.autoKibitz && started == STARTED_NONE && 
2439                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2440                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2441                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "* kibitzes: ")) &&
2442                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2443                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2444                         suppressKibitz = TRUE;
2445                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2446                         next_out = i;
2447                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2448                                 && (gameMode == IcsPlayingWhite)) ||
2449                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2450                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2451                             started = STARTED_CHATTER; // own kibitz we simply discard
2452                         else {
2453                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2454                             parse_pos = 0; parse[0] = NULLCHAR;
2455                             savingComment = TRUE;
2456                             suppressKibitz = gameMode != IcsObserving ? 2 :
2457                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2458                         } 
2459                         continue;
2460                 } else
2461                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2462                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2463                          && atoi(star_match[0])) {
2464                     // suppress the acknowledgements of our own autoKibitz
2465                     char *p;
2466                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2467                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2468                     SendToPlayer(star_match[0], strlen(star_match[0]));
2469                     if(looking_at(buf, &i, "*% ")) // eat prompt
2470                         suppressKibitz = FALSE;
2471                     next_out = i;
2472                     continue;
2473                 }
2474             } // [HGM] kibitz: end of patch
2475
2476 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2477
2478             // [HGM] chat: intercept tells by users for which we have an open chat window
2479             channel = -1;
2480             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2481                                            looking_at(buf, &i, "* whispers:") ||
2482                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2483                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2484                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2485                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2486                 int p;
2487                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2488                 chattingPartner = -1;
2489
2490                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2491                 for(p=0; p<MAX_CHAT; p++) {
2492                     if(channel == atoi(chatPartner[p])) {
2493                     talker[0] = '['; strcat(talker, "] ");
2494                     chattingPartner = p; break;
2495                     }
2496                 } else
2497                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2498                 for(p=0; p<MAX_CHAT; p++) {
2499                     if(!strcmp("WHISPER", chatPartner[p])) {
2500                         talker[0] = '['; strcat(talker, "] ");
2501                         chattingPartner = p; break;
2502                     }
2503                 }
2504                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2505                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2506                     talker[0] = 0;
2507                     chattingPartner = p; break;
2508                 }
2509                 if(chattingPartner<0) i = oldi; else {
2510                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2511                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2512                     started = STARTED_COMMENT;
2513                     parse_pos = 0; parse[0] = NULLCHAR;
2514                     savingComment = 3 + chattingPartner; // counts as TRUE
2515                     suppressKibitz = TRUE;
2516                     continue;
2517                 }
2518             } // [HGM] chat: end of patch
2519
2520             if (appData.zippyTalk || appData.zippyPlay) {
2521                 /* [DM] Backup address for color zippy lines */
2522                 backup = i;
2523 #if ZIPPY
2524        #ifdef WIN32
2525                if (loggedOn == TRUE)
2526                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528        #else
2529                 if (ZippyControl(buf, &i) ||
2530                     ZippyConverse(buf, &i) ||
2531                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532                       loggedOn = TRUE;
2533                       if (!appData.colorize) continue;
2534                 }
2535        #endif
2536 #endif
2537             } // [DM] 'else { ' deleted
2538                 if (
2539                     /* Regular tells and says */
2540                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2542                     looking_at(buf, &i, "* says: ") ||
2543                     /* Don't color "message" or "messages" output */
2544                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545                     looking_at(buf, &i, "*. * at *:*: ") ||
2546                     looking_at(buf, &i, "--* (*:*): ") ||
2547                     /* Message notifications (same color as tells) */
2548                     looking_at(buf, &i, "* has left a message ") ||
2549                     looking_at(buf, &i, "* just sent you a message:\n") ||
2550                     /* Whispers and kibitzes */
2551                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552                     looking_at(buf, &i, "* kibitzes: ") ||
2553                     /* Channel tells */
2554                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555
2556                   if (tkind == 1 && strchr(star_match[0], ':')) {
2557                       /* Avoid "tells you:" spoofs in channels */
2558                      tkind = 3;
2559                   }
2560                   if (star_match[0][0] == NULLCHAR ||
2561                       strchr(star_match[0], ' ') ||
2562                       (tkind == 3 && strchr(star_match[1], ' '))) {
2563                     /* Reject bogus matches */
2564                     i = oldi;
2565                   } else {
2566                     if (appData.colorize) {
2567                       if (oldi > next_out) {
2568                         SendToPlayer(&buf[next_out], oldi - next_out);
2569                         next_out = oldi;
2570                       }
2571                       switch (tkind) {
2572                       case 1:
2573                         Colorize(ColorTell, FALSE);
2574                         curColor = ColorTell;
2575                         break;
2576                       case 2:
2577                         Colorize(ColorKibitz, FALSE);
2578                         curColor = ColorKibitz;
2579                         break;
2580                       case 3:
2581                         p = strrchr(star_match[1], '(');
2582                         if (p == NULL) {
2583                           p = star_match[1];
2584                         } else {
2585                           p++;
2586                         }
2587                         if (atoi(p) == 1) {
2588                           Colorize(ColorChannel1, FALSE);
2589                           curColor = ColorChannel1;
2590                         } else {
2591                           Colorize(ColorChannel, FALSE);
2592                           curColor = ColorChannel;
2593                         }
2594                         break;
2595                       case 5:
2596                         curColor = ColorNormal;
2597                         break;
2598                       }
2599                     }
2600                     if (started == STARTED_NONE && appData.autoComment &&
2601                         (gameMode == IcsObserving ||
2602                          gameMode == IcsPlayingWhite ||
2603                          gameMode == IcsPlayingBlack)) {
2604                       parse_pos = i - oldi;
2605                       memcpy(parse, &buf[oldi], parse_pos);
2606                       parse[parse_pos] = NULLCHAR;
2607                       started = STARTED_COMMENT;
2608                       savingComment = TRUE;
2609                     } else {
2610                       started = STARTED_CHATTER;
2611                       savingComment = FALSE;
2612                     }
2613                     loggedOn = TRUE;
2614                     continue;
2615                   }
2616                 }
2617
2618                 if (looking_at(buf, &i, "* s-shouts: ") ||
2619                     looking_at(buf, &i, "* c-shouts: ")) {
2620                     if (appData.colorize) {
2621                         if (oldi > next_out) {
2622                             SendToPlayer(&buf[next_out], oldi - next_out);
2623                             next_out = oldi;
2624                         }
2625                         Colorize(ColorSShout, FALSE);
2626                         curColor = ColorSShout;
2627                     }
2628                     loggedOn = TRUE;
2629                     started = STARTED_CHATTER;
2630                     continue;
2631                 }
2632
2633                 if (looking_at(buf, &i, "--->")) {
2634                     loggedOn = TRUE;
2635                     continue;
2636                 }
2637
2638                 if (looking_at(buf, &i, "* shouts: ") ||
2639                     looking_at(buf, &i, "--> ")) {
2640                     if (appData.colorize) {
2641                         if (oldi > next_out) {
2642                             SendToPlayer(&buf[next_out], oldi - next_out);
2643                             next_out = oldi;
2644                         }
2645                         Colorize(ColorShout, FALSE);
2646                         curColor = ColorShout;
2647                     }
2648                     loggedOn = TRUE;
2649                     started = STARTED_CHATTER;
2650                     continue;
2651                 }
2652
2653                 if (looking_at( buf, &i, "Challenge:")) {
2654                     if (appData.colorize) {
2655                         if (oldi > next_out) {
2656                             SendToPlayer(&buf[next_out], oldi - next_out);
2657                             next_out = oldi;
2658                         }
2659                         Colorize(ColorChallenge, FALSE);
2660                         curColor = ColorChallenge;
2661                     }
2662                     loggedOn = TRUE;
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* offers you") ||
2667                     looking_at(buf, &i, "* offers to be") ||
2668                     looking_at(buf, &i, "* would like to") ||
2669                     looking_at(buf, &i, "* requests to") ||
2670                     looking_at(buf, &i, "Your opponent offers") ||
2671                     looking_at(buf, &i, "Your opponent requests")) {
2672
2673                     if (appData.colorize) {
2674                         if (oldi > next_out) {
2675                             SendToPlayer(&buf[next_out], oldi - next_out);
2676                             next_out = oldi;
2677                         }
2678                         Colorize(ColorRequest, FALSE);
2679                         curColor = ColorRequest;
2680                     }
2681                     continue;
2682                 }
2683
2684                 if (looking_at(buf, &i, "* (*) seeking")) {
2685                     if (appData.colorize) {
2686                         if (oldi > next_out) {
2687                             SendToPlayer(&buf[next_out], oldi - next_out);
2688                             next_out = oldi;
2689                         }
2690                         Colorize(ColorSeek, FALSE);
2691                         curColor = ColorSeek;
2692                     }
2693                     continue;
2694             }
2695
2696             if (looking_at(buf, &i, "\\   ")) {
2697                 if (prevColor != ColorNormal) {
2698                     if (oldi > next_out) {
2699                         SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = oldi;
2701                     }
2702                     Colorize(prevColor, TRUE);
2703                     curColor = prevColor;
2704                 }
2705                 if (savingComment) {
2706                     parse_pos = i - oldi;
2707                     memcpy(parse, &buf[oldi], parse_pos);
2708                     parse[parse_pos] = NULLCHAR;
2709                     started = STARTED_COMMENT;
2710                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2711                         chattingPartner = savingComment - 3; // kludge to remember the box
2712                 } else {
2713                     started = STARTED_CHATTER;
2714                 }
2715                 continue;
2716             }
2717
2718             if (looking_at(buf, &i, "Black Strength :") ||
2719                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2720                 looking_at(buf, &i, "<10>") ||
2721                 looking_at(buf, &i, "#@#")) {
2722                 /* Wrong board style */
2723                 loggedOn = TRUE;
2724                 SendToICS(ics_prefix);
2725                 SendToICS("set style 12\n");
2726                 SendToICS(ics_prefix);
2727                 SendToICS("refresh\n");
2728                 continue;
2729             }
2730             
2731             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2732                 ICSInitScript();
2733                 have_sent_ICS_logon = 1;
2734                 continue;
2735             }
2736               
2737             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2738                 (looking_at(buf, &i, "\n<12> ") ||
2739                  looking_at(buf, &i, "<12> "))) {
2740                 loggedOn = TRUE;
2741                 if (oldi > next_out) {
2742                     SendToPlayer(&buf[next_out], oldi - next_out);
2743                 }
2744                 next_out = i;
2745                 started = STARTED_BOARD;
2746                 parse_pos = 0;
2747                 continue;
2748             }
2749
2750             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2751                 looking_at(buf, &i, "<b1> ")) {
2752                 if (oldi > next_out) {
2753                     SendToPlayer(&buf[next_out], oldi - next_out);
2754                 }
2755                 next_out = i;
2756                 started = STARTED_HOLDINGS;
2757                 parse_pos = 0;
2758                 continue;
2759             }
2760
2761             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2762                 loggedOn = TRUE;
2763                 /* Header for a move list -- first line */
2764
2765                 switch (ics_getting_history) {
2766                   case H_FALSE:
2767                     switch (gameMode) {
2768                       case IcsIdle:
2769                       case BeginningOfGame:
2770                         /* User typed "moves" or "oldmoves" while we
2771                            were idle.  Pretend we asked for these
2772                            moves and soak them up so user can step
2773                            through them and/or save them.
2774                            */
2775                         Reset(FALSE, TRUE);
2776                         gameMode = IcsObserving;
2777                         ModeHighlight();
2778                         ics_gamenum = -1;
2779                         ics_getting_history = H_GOT_UNREQ_HEADER;
2780                         break;
2781                       case EditGame: /*?*/
2782                       case EditPosition: /*?*/
2783                         /* Should above feature work in these modes too? */
2784                         /* For now it doesn't */
2785                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2786                         break;
2787                       default:
2788                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2789                         break;
2790                     }
2791                     break;
2792                   case H_REQUESTED:
2793                     /* Is this the right one? */
2794                     if (gameInfo.white && gameInfo.black &&
2795                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2796                         strcmp(gameInfo.black, star_match[2]) == 0) {
2797                         /* All is well */
2798                         ics_getting_history = H_GOT_REQ_HEADER;
2799                     }
2800                     break;
2801                   case H_GOT_REQ_HEADER:
2802                   case H_GOT_UNREQ_HEADER:
2803                   case H_GOT_UNWANTED_HEADER:
2804                   case H_GETTING_MOVES:
2805                     /* Should not happen */
2806                     DisplayError(_("Error gathering move list: two headers"), 0);
2807                     ics_getting_history = H_FALSE;
2808                     break;
2809                 }
2810
2811                 /* Save player ratings into gameInfo if needed */
2812                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2813                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2814                     (gameInfo.whiteRating == -1 ||
2815                      gameInfo.blackRating == -1)) {
2816
2817                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2818                     gameInfo.blackRating = string_to_rating(star_match[3]);
2819                     if (appData.debugMode)
2820                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2821                               gameInfo.whiteRating, gameInfo.blackRating);
2822                 }
2823                 continue;
2824             }
2825
2826             if (looking_at(buf, &i,
2827               "* * match, initial time: * minute*, increment: * second")) {
2828                 /* Header for a move list -- second line */
2829                 /* Initial board will follow if this is a wild game */
2830                 if (gameInfo.event != NULL) free(gameInfo.event);
2831                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2832                 gameInfo.event = StrSave(str);
2833                 /* [HGM] we switched variant. Translate boards if needed. */
2834                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2835                 continue;
2836             }
2837
2838             if (looking_at(buf, &i, "Move  ")) {
2839                 /* Beginning of a move list */
2840                 switch (ics_getting_history) {
2841                   case H_FALSE:
2842                     /* Normally should not happen */
2843                     /* Maybe user hit reset while we were parsing */
2844                     break;
2845                   case H_REQUESTED:
2846                     /* Happens if we are ignoring a move list that is not
2847                      * the one we just requested.  Common if the user
2848                      * tries to observe two games without turning off
2849                      * getMoveList */
2850                     break;
2851                   case H_GETTING_MOVES:
2852                     /* Should not happen */
2853                     DisplayError(_("Error gathering move list: nested"), 0);
2854                     ics_getting_history = H_FALSE;
2855                     break;
2856                   case H_GOT_REQ_HEADER:
2857                     ics_getting_history = H_GETTING_MOVES;
2858                     started = STARTED_MOVES;
2859                     parse_pos = 0;
2860                     if (oldi > next_out) {
2861                         SendToPlayer(&buf[next_out], oldi - next_out);
2862                     }
2863                     break;
2864                   case H_GOT_UNREQ_HEADER:
2865                     ics_getting_history = H_GETTING_MOVES;
2866                     started = STARTED_MOVES_NOHIDE;
2867                     parse_pos = 0;
2868                     break;
2869                   case H_GOT_UNWANTED_HEADER:
2870                     ics_getting_history = H_FALSE;
2871                     break;
2872                 }
2873                 continue;
2874             }                           
2875             
2876             if (looking_at(buf, &i, "% ") ||
2877                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2878                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2879                 if(suppressKibitz) next_out = i;
2880                 savingComment = FALSE;
2881                 suppressKibitz = 0;
2882                 switch (started) {
2883                   case STARTED_MOVES:
2884                   case STARTED_MOVES_NOHIDE:
2885                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2886                     parse[parse_pos + i - oldi] = NULLCHAR;
2887                     ParseGameHistory(parse);
2888 #if ZIPPY
2889                     if (appData.zippyPlay && first.initDone) {
2890                         FeedMovesToProgram(&first, forwardMostMove);
2891                         if (gameMode == IcsPlayingWhite) {
2892                             if (WhiteOnMove(forwardMostMove)) {
2893                                 if (first.sendTime) {
2894                                   if (first.useColors) {
2895                                     SendToProgram("black\n", &first); 
2896                                   }
2897                                   SendTimeRemaining(&first, TRUE);
2898                                 }
2899                                 if (first.useColors) {
2900                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2901                                 }
2902                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2903                                 first.maybeThinking = TRUE;
2904                             } else {
2905                                 if (first.usePlayother) {
2906                                   if (first.sendTime) {
2907                                     SendTimeRemaining(&first, TRUE);
2908                                   }
2909                                   SendToProgram("playother\n", &first);
2910                                   firstMove = FALSE;
2911                                 } else {
2912                                   firstMove = TRUE;
2913                                 }
2914                             }
2915                         } else if (gameMode == IcsPlayingBlack) {
2916                             if (!WhiteOnMove(forwardMostMove)) {
2917                                 if (first.sendTime) {
2918                                   if (first.useColors) {
2919                                     SendToProgram("white\n", &first);
2920                                   }
2921                                   SendTimeRemaining(&first, FALSE);
2922                                 }
2923                                 if (first.useColors) {
2924                                   SendToProgram("black\n", &first);
2925                                 }
2926                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2927                                 first.maybeThinking = TRUE;
2928                             } else {
2929                                 if (first.usePlayother) {
2930                                   if (first.sendTime) {
2931                                     SendTimeRemaining(&first, FALSE);
2932                                   }
2933                                   SendToProgram("playother\n", &first);
2934                                   firstMove = FALSE;
2935                                 } else {
2936                                   firstMove = TRUE;
2937                                 }
2938                             }
2939                         }                       
2940                     }
2941 #endif
2942                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2943                         /* Moves came from oldmoves or moves command
2944                            while we weren't doing anything else.
2945                            */
2946                         currentMove = forwardMostMove;
2947                         ClearHighlights();/*!!could figure this out*/
2948                         flipView = appData.flipView;
2949                         DrawPosition(TRUE, boards[currentMove]);
2950                         DisplayBothClocks();
2951                         sprintf(str, "%s vs. %s",
2952                                 gameInfo.white, gameInfo.black);
2953                         DisplayTitle(str);
2954                         gameMode = IcsIdle;
2955                     } else {
2956                         /* Moves were history of an active game */
2957                         if (gameInfo.resultDetails != NULL) {
2958                             free(gameInfo.resultDetails);
2959                             gameInfo.resultDetails = NULL;
2960                         }
2961                     }
2962                     HistorySet(parseList, backwardMostMove,
2963                                forwardMostMove, currentMove-1);
2964                     DisplayMove(currentMove - 1);
2965                     if (started == STARTED_MOVES) next_out = i;
2966                     started = STARTED_NONE;
2967                     ics_getting_history = H_FALSE;
2968                     break;
2969
2970                   case STARTED_OBSERVE:
2971                     started = STARTED_NONE;
2972                     SendToICS(ics_prefix);
2973                     SendToICS("refresh\n");
2974                     break;
2975
2976                   default:
2977                     break;
2978                 }
2979                 if(bookHit) { // [HGM] book: simulate book reply
2980                     static char bookMove[MSG_SIZ]; // a bit generous?
2981
2982                     programStats.nodes = programStats.depth = programStats.time = 
2983                     programStats.score = programStats.got_only_move = 0;
2984                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2985
2986                     strcpy(bookMove, "move ");
2987                     strcat(bookMove, bookHit);
2988                     HandleMachineMove(bookMove, &first);
2989                 }
2990                 continue;
2991             }
2992             
2993             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2994                  started == STARTED_HOLDINGS ||
2995                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2996                 /* Accumulate characters in move list or board */
2997                 parse[parse_pos++] = buf[i];
2998             }
2999             
3000             /* Start of game messages.  Mostly we detect start of game
3001                when the first board image arrives.  On some versions
3002                of the ICS, though, we need to do a "refresh" after starting
3003                to observe in order to get the current board right away. */
3004             if (looking_at(buf, &i, "Adding game * to observation list")) {
3005                 started = STARTED_OBSERVE;
3006                 continue;
3007             }
3008
3009             /* Handle auto-observe */
3010             if (appData.autoObserve &&
3011                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3012                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3013                 char *player;
3014                 /* Choose the player that was highlighted, if any. */
3015                 if (star_match[0][0] == '\033' ||
3016                     star_match[1][0] != '\033') {
3017                     player = star_match[0];
3018                 } else {
3019                     player = star_match[2];
3020                 }
3021                 sprintf(str, "%sobserve %s\n",
3022                         ics_prefix, StripHighlightAndTitle(player));
3023                 SendToICS(str);
3024
3025                 /* Save ratings from notify string */
3026                 strcpy(player1Name, star_match[0]);
3027                 player1Rating = string_to_rating(star_match[1]);
3028                 strcpy(player2Name, star_match[2]);
3029                 player2Rating = string_to_rating(star_match[3]);
3030
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, 
3033                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3034                           player1Name, player1Rating,
3035                           player2Name, player2Rating);
3036
3037                 continue;
3038             }
3039
3040             /* Deal with automatic examine mode after a game,
3041                and with IcsObserving -> IcsExamining transition */
3042             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3043                 looking_at(buf, &i, "has made you an examiner of game *")) {
3044
3045                 int gamenum = atoi(star_match[0]);
3046                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3047                     gamenum == ics_gamenum) {
3048                     /* We were already playing or observing this game;
3049                        no need to refetch history */
3050                     gameMode = IcsExamining;
3051                     if (pausing) {
3052                         pauseExamForwardMostMove = forwardMostMove;
3053                     } else if (currentMove < forwardMostMove) {
3054                         ForwardInner(forwardMostMove);
3055                     }
3056                 } else {
3057                     /* I don't think this case really can happen */
3058                     SendToICS(ics_prefix);
3059                     SendToICS("refresh\n");
3060                 }
3061                 continue;
3062             }    
3063             
3064             /* Error messages */
3065 //          if (ics_user_moved) {
3066             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3067                 if (looking_at(buf, &i, "Illegal move") ||
3068                     looking_at(buf, &i, "Not a legal move") ||
3069                     looking_at(buf, &i, "Your king is in check") ||
3070                     looking_at(buf, &i, "It isn't your turn") ||
3071                     looking_at(buf, &i, "It is not your move")) {
3072                     /* Illegal move */
3073                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3074                         currentMove = forwardMostMove-1;
3075                         DisplayMove(currentMove - 1); /* before DMError */
3076                         DrawPosition(FALSE, boards[currentMove]);
3077                         SwitchClocks(forwardMostMove-1); // [HGM] race
3078                         DisplayBothClocks();
3079                     }
3080                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3081                     ics_user_moved = 0;
3082                     continue;
3083                 }
3084             }
3085
3086             if (looking_at(buf, &i, "still have time") ||
3087                 looking_at(buf, &i, "not out of time") ||
3088                 looking_at(buf, &i, "either player is out of time") ||
3089                 looking_at(buf, &i, "has timeseal; checking")) {
3090                 /* We must have called his flag a little too soon */
3091                 whiteFlag = blackFlag = FALSE;
3092                 continue;
3093             }
3094
3095             if (looking_at(buf, &i, "added * seconds to") ||
3096                 looking_at(buf, &i, "seconds were added to")) {
3097                 /* Update the clocks */
3098                 SendToICS(ics_prefix);
3099                 SendToICS("refresh\n");
3100                 continue;
3101             }
3102
3103             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3104                 ics_clock_paused = TRUE;
3105                 StopClocks();
3106                 continue;
3107             }
3108
3109             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3110                 ics_clock_paused = FALSE;
3111                 StartClocks();
3112                 continue;
3113             }
3114
3115             /* Grab player ratings from the Creating: message.
3116                Note we have to check for the special case when
3117                the ICS inserts things like [white] or [black]. */
3118             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3119                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3120                 /* star_matches:
3121                    0    player 1 name (not necessarily white)
3122                    1    player 1 rating
3123                    2    empty, white, or black (IGNORED)
3124                    3    player 2 name (not necessarily black)
3125                    4    player 2 rating
3126                    
3127                    The names/ratings are sorted out when the game
3128                    actually starts (below).
3129                 */
3130                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3131                 player1Rating = string_to_rating(star_match[1]);
3132                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3133                 player2Rating = string_to_rating(star_match[4]);
3134
3135                 if (appData.debugMode)
3136                   fprintf(debugFP, 
3137                           "Ratings from 'Creating:' %s %d, %s %d\n",
3138                           player1Name, player1Rating,
3139                           player2Name, player2Rating);
3140
3141                 continue;
3142             }
3143             
3144             /* Improved generic start/end-of-game messages */
3145             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3146                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3147                 /* If tkind == 0: */
3148                 /* star_match[0] is the game number */
3149                 /*           [1] is the white player's name */
3150                 /*           [2] is the black player's name */
3151                 /* For end-of-game: */
3152                 /*           [3] is the reason for the game end */
3153                 /*           [4] is a PGN end game-token, preceded by " " */
3154                 /* For start-of-game: */
3155                 /*           [3] begins with "Creating" or "Continuing" */
3156                 /*           [4] is " *" or empty (don't care). */
3157                 int gamenum = atoi(star_match[0]);
3158                 char *whitename, *blackname, *why, *endtoken;
3159                 ChessMove endtype = (ChessMove) 0;
3160
3161                 if (tkind == 0) {
3162                   whitename = star_match[1];
3163                   blackname = star_match[2];
3164                   why = star_match[3];
3165                   endtoken = star_match[4];
3166                 } else {
3167                   whitename = star_match[1];
3168                   blackname = star_match[3];
3169                   why = star_match[5];
3170                   endtoken = star_match[6];
3171                 }
3172
3173                 /* Game start messages */
3174                 if (strncmp(why, "Creating ", 9) == 0 ||
3175                     strncmp(why, "Continuing ", 11) == 0) {
3176                     gs_gamenum = gamenum;
3177                     strcpy(gs_kind, strchr(why, ' ') + 1);
3178                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3179 #if ZIPPY
3180                     if (appData.zippyPlay) {
3181                         ZippyGameStart(whitename, blackname);
3182                     }
3183 #endif /*ZIPPY*/
3184                     continue;
3185                 }
3186
3187                 /* Game end messages */
3188                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3189                     ics_gamenum != gamenum) {
3190                     continue;
3191                 }
3192                 while (endtoken[0] == ' ') endtoken++;
3193                 switch (endtoken[0]) {
3194                   case '*':
3195                   default:
3196                     endtype = GameUnfinished;
3197                     break;
3198                   case '0':
3199                     endtype = BlackWins;
3200                     break;
3201                   case '1':
3202                     if (endtoken[1] == '/')
3203                       endtype = GameIsDrawn;
3204                     else
3205                       endtype = WhiteWins;
3206                     break;
3207                 }
3208                 GameEnds(endtype, why, GE_ICS);
3209 #if ZIPPY
3210                 if (appData.zippyPlay && first.initDone) {
3211                     ZippyGameEnd(endtype, why);
3212                     if (first.pr == NULL) {
3213                       /* Start the next process early so that we'll
3214                          be ready for the next challenge */
3215                       StartChessProgram(&first);
3216                     }
3217                     /* Send "new" early, in case this command takes
3218                        a long time to finish, so that we'll be ready
3219                        for the next challenge. */
3220                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3221                     Reset(TRUE, TRUE);
3222                 }
3223 #endif /*ZIPPY*/
3224                 continue;
3225             }
3226
3227             if (looking_at(buf, &i, "Removing game * from observation") ||
3228                 looking_at(buf, &i, "no longer observing game *") ||
3229                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3230                 if (gameMode == IcsObserving &&
3231                     atoi(star_match[0]) == ics_gamenum)
3232                   {
3233                       /* icsEngineAnalyze */
3234                       if (appData.icsEngineAnalyze) {
3235                             ExitAnalyzeMode();
3236                             ModeHighlight();
3237                       }
3238                       StopClocks();
3239                       gameMode = IcsIdle;
3240                       ics_gamenum = -1;
3241                       ics_user_moved = FALSE;
3242                   }
3243                 continue;
3244             }
3245
3246             if (looking_at(buf, &i, "no longer examining game *")) {
3247                 if (gameMode == IcsExamining &&
3248                     atoi(star_match[0]) == ics_gamenum)
3249                   {
3250                       gameMode = IcsIdle;
3251                       ics_gamenum = -1;
3252                       ics_user_moved = FALSE;
3253                   }
3254                 continue;
3255             }
3256
3257             /* Advance leftover_start past any newlines we find,
3258                so only partial lines can get reparsed */
3259             if (looking_at(buf, &i, "\n")) {
3260                 prevColor = curColor;
3261                 if (curColor != ColorNormal) {
3262                     if (oldi > next_out) {
3263                         SendToPlayer(&buf[next_out], oldi - next_out);
3264                         next_out = oldi;
3265                     }
3266                     Colorize(ColorNormal, FALSE);
3267                     curColor = ColorNormal;
3268                 }
3269                 if (started == STARTED_BOARD) {
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     ParseBoard12(parse);
3273                     ics_user_moved = 0;
3274
3275                     /* Send premove here */
3276                     if (appData.premove) {
3277                       char str[MSG_SIZ];
3278                       if (currentMove == 0 &&
3279                           gameMode == IcsPlayingWhite &&
3280                           appData.premoveWhite) {
3281                         sprintf(str, "%s\n", appData.premoveWhiteText);
3282                         if (appData.debugMode)
3283                           fprintf(debugFP, "Sending premove:\n");
3284                         SendToICS(str);
3285                       } else if (currentMove == 1 &&
3286                                  gameMode == IcsPlayingBlack &&
3287                                  appData.premoveBlack) {
3288                         sprintf(str, "%s\n", appData.premoveBlackText);
3289                         if (appData.debugMode)
3290                           fprintf(debugFP, "Sending premove:\n");
3291                         SendToICS(str);
3292                       } else if (gotPremove) {
3293                         gotPremove = 0;
3294                         ClearPremoveHighlights();
3295                         if (appData.debugMode)
3296                           fprintf(debugFP, "Sending premove:\n");
3297                           UserMoveEvent(premoveFromX, premoveFromY, 
3298                                         premoveToX, premoveToY, 
3299                                         premovePromoChar);
3300                       }
3301                     }
3302
3303                     /* Usually suppress following prompt */
3304                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3305                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3306                         if (looking_at(buf, &i, "*% ")) {
3307                             savingComment = FALSE;
3308                             suppressKibitz = 0;
3309                         }
3310                     }
3311                     next_out = i;
3312                 } else if (started == STARTED_HOLDINGS) {
3313                     int gamenum;
3314                     char new_piece[MSG_SIZ];
3315                     started = STARTED_NONE;
3316                     parse[parse_pos] = NULLCHAR;
3317                     if (appData.debugMode)
3318                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3319                                                         parse, currentMove);
3320                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3321                         gamenum == ics_gamenum) {
3322                         if (gameInfo.variant == VariantNormal) {
3323                           /* [HGM] We seem to switch variant during a game!
3324                            * Presumably no holdings were displayed, so we have
3325                            * to move the position two files to the right to
3326                            * create room for them!
3327                            */
3328                           VariantClass newVariant;
3329                           switch(gameInfo.boardWidth) { // base guess on board width
3330                                 case 9:  newVariant = VariantShogi; break;
3331                                 case 10: newVariant = VariantGreat; break;
3332                                 default: newVariant = VariantCrazyhouse; break;
3333                           }
3334                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3335                           /* Get a move list just to see the header, which
3336                              will tell us whether this is really bug or zh */
3337                           if (ics_getting_history == H_FALSE) {
3338                             ics_getting_history = H_REQUESTED;
3339                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3340                             SendToICS(str);
3341                           }
3342                         }
3343                         new_piece[0] = NULLCHAR;
3344                         sscanf(parse, "game %d white [%s black [%s <- %s",
3345                                &gamenum, white_holding, black_holding,
3346                                new_piece);
3347                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3348                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3349                         /* [HGM] copy holdings to board holdings area */
3350                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3351                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3352                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3353 #if ZIPPY
3354                         if (appData.zippyPlay && first.initDone) {
3355                             ZippyHoldings(white_holding, black_holding,
3356                                           new_piece);
3357                         }
3358 #endif /*ZIPPY*/
3359                         if (tinyLayout || smallLayout) {
3360                             char wh[16], bh[16];
3361                             PackHolding(wh, white_holding);
3362                             PackHolding(bh, black_holding);
3363                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3364                                     gameInfo.white, gameInfo.black);
3365                         } else {
3366                             sprintf(str, "%s [%s] vs. %s [%s]",
3367                                     gameInfo.white, white_holding,
3368                                     gameInfo.black, black_holding);
3369                         }
3370
3371                         DrawPosition(FALSE, boards[currentMove]);
3372                         DisplayTitle(str);
3373                     }
3374                     /* Suppress following prompt */
3375                     if (looking_at(buf, &i, "*% ")) {
3376                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3377                         savingComment = FALSE;
3378                         suppressKibitz = 0;
3379                     }
3380                     next_out = i;
3381                 }
3382                 continue;
3383             }
3384
3385             i++;                /* skip unparsed character and loop back */
3386         }
3387         
3388         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3389 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3390 //          SendToPlayer(&buf[next_out], i - next_out);
3391             started != STARTED_HOLDINGS && leftover_start > next_out) {
3392             SendToPlayer(&buf[next_out], leftover_start - next_out);
3393             next_out = i;
3394         }
3395         
3396         leftover_len = buf_len - leftover_start;
3397         /* if buffer ends with something we couldn't parse,
3398            reparse it after appending the next read */
3399         
3400     } else if (count == 0) {
3401         RemoveInputSource(isr);
3402         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3403     } else {
3404         DisplayFatalError(_("Error reading from ICS"), error, 1);
3405     }
3406 }
3407
3408
3409 /* Board style 12 looks like this:
3410    
3411    <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
3412    
3413  * The "<12> " is stripped before it gets to this routine.  The two
3414  * trailing 0's (flip state and clock ticking) are later addition, and
3415  * some chess servers may not have them, or may have only the first.
3416  * Additional trailing fields may be added in the future.  
3417  */
3418
3419 #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"
3420
3421 #define RELATION_OBSERVING_PLAYED    0
3422 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3423 #define RELATION_PLAYING_MYMOVE      1
3424 #define RELATION_PLAYING_NOTMYMOVE  -1
3425 #define RELATION_EXAMINING           2
3426 #define RELATION_ISOLATED_BOARD     -3
3427 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3428
3429 void
3430 ParseBoard12(string)
3431      char *string;
3432
3433     GameMode newGameMode;
3434     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3435     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3436     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3437     char to_play, board_chars[200];
3438     char move_str[500], str[500], elapsed_time[500];
3439     char black[32], white[32];
3440     Board board;
3441     int prevMove = currentMove;
3442     int ticking = 2;
3443     ChessMove moveType;
3444     int fromX, fromY, toX, toY;
3445     char promoChar;
3446     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3447     char *bookHit = NULL; // [HGM] book
3448     Boolean weird = FALSE, reqFlag = FALSE;
3449
3450     fromX = fromY = toX = toY = -1;
3451     
3452     newGame = FALSE;
3453
3454     if (appData.debugMode)
3455       fprintf(debugFP, _("Parsing board: %s\n"), string);
3456
3457     move_str[0] = NULLCHAR;
3458     elapsed_time[0] = NULLCHAR;
3459     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3460         int  i = 0, j;
3461         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3462             if(string[i] == ' ') { ranks++; files = 0; }
3463             else files++;
3464             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3465             i++;
3466         }
3467         for(j = 0; j <i; j++) board_chars[j] = string[j];
3468         board_chars[i] = '\0';
3469         string += i + 1;
3470     }
3471     n = sscanf(string, PATTERN, &to_play, &double_push,
3472                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3473                &gamenum, white, black, &relation, &basetime, &increment,
3474                &white_stren, &black_stren, &white_time, &black_time,
3475                &moveNum, str, elapsed_time, move_str, &ics_flip,
3476                &ticking);
3477
3478     if (n < 21) {
3479         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3480         DisplayError(str, 0);
3481         return;
3482     }
3483
3484     /* Convert the move number to internal form */
3485     moveNum = (moveNum - 1) * 2;
3486     if (to_play == 'B') moveNum++;
3487     if (moveNum >= MAX_MOVES) {
3488       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3489                         0, 1);
3490       return;
3491     }
3492     
3493     switch (relation) {
3494       case RELATION_OBSERVING_PLAYED:
3495       case RELATION_OBSERVING_STATIC:
3496         if (gamenum == -1) {
3497             /* Old ICC buglet */
3498             relation = RELATION_OBSERVING_STATIC;
3499         }
3500         newGameMode = IcsObserving;
3501         break;
3502       case RELATION_PLAYING_MYMOVE:
3503       case RELATION_PLAYING_NOTMYMOVE:
3504         newGameMode =
3505           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3506             IcsPlayingWhite : IcsPlayingBlack;
3507         break;
3508       case RELATION_EXAMINING:
3509         newGameMode = IcsExamining;
3510         break;
3511       case RELATION_ISOLATED_BOARD:
3512       default:
3513         /* Just display this board.  If user was doing something else,
3514            we will forget about it until the next board comes. */ 
3515         newGameMode = IcsIdle;
3516         break;
3517       case RELATION_STARTING_POSITION:
3518         newGameMode = gameMode;
3519         break;
3520     }
3521     
3522     /* Modify behavior for initial board display on move listing
3523        of wild games.
3524        */
3525     switch (ics_getting_history) {
3526       case H_FALSE:
3527       case H_REQUESTED:
3528         break;
3529       case H_GOT_REQ_HEADER:
3530       case H_GOT_UNREQ_HEADER:
3531         /* This is the initial position of the current game */
3532         gamenum = ics_gamenum;
3533         moveNum = 0;            /* old ICS bug workaround */
3534         if (to_play == 'B') {
3535           startedFromSetupPosition = TRUE;
3536           blackPlaysFirst = TRUE;
3537           moveNum = 1;
3538           if (forwardMostMove == 0) forwardMostMove = 1;
3539           if (backwardMostMove == 0) backwardMostMove = 1;
3540           if (currentMove == 0) currentMove = 1;
3541         }
3542         newGameMode = gameMode;
3543         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3544         break;
3545       case H_GOT_UNWANTED_HEADER:
3546         /* This is an initial board that we don't want */
3547         return;
3548       case H_GETTING_MOVES:
3549         /* Should not happen */
3550         DisplayError(_("Error gathering move list: extra board"), 0);
3551         ics_getting_history = H_FALSE;
3552         return;
3553     }
3554
3555    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3556                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3557      /* [HGM] We seem to have switched variant unexpectedly
3558       * Try to guess new variant from board size
3559       */
3560           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3561           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3562           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3563           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3564           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3565           if(!weird) newVariant = VariantNormal;
3566           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3567           /* Get a move list just to see the header, which
3568              will tell us whether this is really bug or zh */
3569           if (ics_getting_history == H_FALSE) {
3570             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3571             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3572             SendToICS(str);
3573           }
3574     }
3575     
3576     /* Take action if this is the first board of a new game, or of a
3577        different game than is currently being displayed.  */
3578     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3579         relation == RELATION_ISOLATED_BOARD) {
3580         
3581         /* Forget the old game and get the history (if any) of the new one */
3582         if (gameMode != BeginningOfGame) {
3583           Reset(TRUE, TRUE);
3584         }
3585         newGame = TRUE;
3586         if (appData.autoRaiseBoard) BoardToTop();
3587         prevMove = -3;
3588         if (gamenum == -1) {
3589             newGameMode = IcsIdle;
3590         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3591                    appData.getMoveList && !reqFlag) {
3592             /* Need to get game history */
3593             ics_getting_history = H_REQUESTED;
3594             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3595             SendToICS(str);
3596         }
3597         
3598         /* Initially flip the board to have black on the bottom if playing
3599            black or if the ICS flip flag is set, but let the user change
3600            it with the Flip View button. */
3601         flipView = appData.autoFlipView ? 
3602           (newGameMode == IcsPlayingBlack) || ics_flip :
3603           appData.flipView;
3604         
3605         /* Done with values from previous mode; copy in new ones */
3606         gameMode = newGameMode;
3607         ModeHighlight();
3608         ics_gamenum = gamenum;
3609         if (gamenum == gs_gamenum) {
3610             int klen = strlen(gs_kind);
3611             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3612             sprintf(str, "ICS %s", gs_kind);
3613             gameInfo.event = StrSave(str);
3614         } else {
3615             gameInfo.event = StrSave("ICS game");
3616         }
3617         gameInfo.site = StrSave(appData.icsHost);
3618         gameInfo.date = PGNDate();
3619         gameInfo.round = StrSave("-");
3620         gameInfo.white = StrSave(white);
3621         gameInfo.black = StrSave(black);
3622         timeControl = basetime * 60 * 1000;
3623         timeControl_2 = 0;
3624         timeIncrement = increment * 1000;
3625         movesPerSession = 0;
3626         gameInfo.timeControl = TimeControlTagValue();
3627         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3628   if (appData.debugMode) {
3629     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3630     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3631     setbuf(debugFP, NULL);
3632   }
3633
3634         gameInfo.outOfBook = NULL;
3635         
3636         /* Do we have the ratings? */
3637         if (strcmp(player1Name, white) == 0 &&
3638             strcmp(player2Name, black) == 0) {
3639             if (appData.debugMode)
3640               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3641                       player1Rating, player2Rating);
3642             gameInfo.whiteRating = player1Rating;
3643             gameInfo.blackRating = player2Rating;
3644         } else if (strcmp(player2Name, white) == 0 &&
3645                    strcmp(player1Name, black) == 0) {
3646             if (appData.debugMode)
3647               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3648                       player2Rating, player1Rating);
3649             gameInfo.whiteRating = player2Rating;
3650             gameInfo.blackRating = player1Rating;
3651         }
3652         player1Name[0] = player2Name[0] = NULLCHAR;
3653
3654         /* Silence shouts if requested */
3655         if (appData.quietPlay &&
3656             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3657             SendToICS(ics_prefix);
3658             SendToICS("set shout 0\n");
3659         }
3660     }
3661     
3662     /* Deal with midgame name changes */
3663     if (!newGame) {
3664         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3665             if (gameInfo.white) free(gameInfo.white);
3666             gameInfo.white = StrSave(white);
3667         }
3668         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3669             if (gameInfo.black) free(gameInfo.black);
3670             gameInfo.black = StrSave(black);
3671         }
3672     }
3673     
3674     /* Throw away game result if anything actually changes in examine mode */
3675     if (gameMode == IcsExamining && !newGame) {
3676         gameInfo.result = GameUnfinished;
3677         if (gameInfo.resultDetails != NULL) {
3678             free(gameInfo.resultDetails);
3679             gameInfo.resultDetails = NULL;
3680         }
3681     }
3682     
3683     /* In pausing && IcsExamining mode, we ignore boards coming
3684        in if they are in a different variation than we are. */
3685     if (pauseExamInvalid) return;
3686     if (pausing && gameMode == IcsExamining) {
3687         if (moveNum <= pauseExamForwardMostMove) {
3688             pauseExamInvalid = TRUE;
3689             forwardMostMove = pauseExamForwardMostMove;
3690             return;
3691         }
3692     }
3693     
3694   if (appData.debugMode) {
3695     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3696   }
3697     /* Parse the board */
3698     for (k = 0; k < ranks; k++) {
3699       for (j = 0; j < files; j++)
3700         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3701       if(gameInfo.holdingsWidth > 1) {
3702            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3703            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3704       }
3705     }
3706     CopyBoard(boards[moveNum], board);
3707     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3708     if (moveNum == 0) {
3709         startedFromSetupPosition =
3710           !CompareBoards(board, initialPosition);
3711         if(startedFromSetupPosition)
3712             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3713     }
3714
3715     /* [HGM] Set castling rights. Take the outermost Rooks,
3716        to make it also work for FRC opening positions. Note that board12
3717        is really defective for later FRC positions, as it has no way to
3718        indicate which Rook can castle if they are on the same side of King.
3719        For the initial position we grant rights to the outermost Rooks,
3720        and remember thos rights, and we then copy them on positions
3721        later in an FRC game. This means WB might not recognize castlings with
3722        Rooks that have moved back to their original position as illegal,
3723        but in ICS mode that is not its job anyway.
3724     */
3725     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3726     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3727
3728         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3729             if(board[0][i] == WhiteRook) j = i;
3730         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3731         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3732             if(board[0][i] == WhiteRook) j = i;
3733         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3734         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3735             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3737         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3738             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3739         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3740
3741         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3742         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3744         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3745             if(board[BOARD_HEIGHT-1][k] == bKing)
3746                 initialRights[5] = castlingRights[moveNum][5] = k;
3747         if(gameInfo.variant == VariantTwoKings) {
3748             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3749             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3750             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3751         }
3752     } else { int r;
3753         r = castlingRights[moveNum][0] = initialRights[0];
3754         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3755         r = castlingRights[moveNum][1] = initialRights[1];
3756         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3757         r = castlingRights[moveNum][3] = initialRights[3];
3758         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3759         r = castlingRights[moveNum][4] = initialRights[4];
3760         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3761         /* wildcastle kludge: always assume King has rights */
3762         r = castlingRights[moveNum][2] = initialRights[2];
3763         r = castlingRights[moveNum][5] = initialRights[5];
3764     }
3765     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3766     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3767
3768     
3769     if (ics_getting_history == H_GOT_REQ_HEADER ||
3770         ics_getting_history == H_GOT_UNREQ_HEADER) {
3771         /* This was an initial position from a move list, not
3772            the current position */
3773         return;
3774     }
3775     
3776     /* Update currentMove and known move number limits */
3777     newMove = newGame || moveNum > forwardMostMove;
3778
3779     if (newGame) {
3780         forwardMostMove = backwardMostMove = currentMove = moveNum;
3781         if (gameMode == IcsExamining && moveNum == 0) {
3782           /* Workaround for ICS limitation: we are not told the wild
3783              type when starting to examine a game.  But if we ask for
3784              the move list, the move list header will tell us */
3785             ics_getting_history = H_REQUESTED;
3786             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3787             SendToICS(str);
3788         }
3789     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3790                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3791 #if ZIPPY
3792         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3793         /* [HGM] applied this also to an engine that is silently watching        */
3794         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3795             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3796             gameInfo.variant == currentlyInitializedVariant) {
3797           takeback = forwardMostMove - moveNum;
3798           for (i = 0; i < takeback; i++) {
3799             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3800             SendToProgram("undo\n", &first);
3801           }
3802         }
3803 #endif
3804
3805         forwardMostMove = moveNum;
3806         if (!pausing || currentMove > forwardMostMove)
3807           currentMove = forwardMostMove;
3808     } else {
3809         /* New part of history that is not contiguous with old part */ 
3810         if (pausing && gameMode == IcsExamining) {
3811             pauseExamInvalid = TRUE;
3812             forwardMostMove = pauseExamForwardMostMove;
3813             return;
3814         }
3815         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3816 #if ZIPPY
3817             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3818                 // [HGM] when we will receive the move list we now request, it will be
3819                 // fed to the engine from the first move on. So if the engine is not
3820                 // in the initial position now, bring it there.
3821                 InitChessProgram(&first, 0);
3822             }
3823 #endif
3824             ics_getting_history = H_REQUESTED;
3825             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3826             SendToICS(str);
3827         }
3828         forwardMostMove = backwardMostMove = currentMove = moveNum;
3829     }
3830     
3831     /* Update the clocks */
3832     if (strchr(elapsed_time, '.')) {
3833       /* Time is in ms */
3834       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3835       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3836     } else {
3837       /* Time is in seconds */
3838       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3839       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3840     }
3841       
3842
3843 #if ZIPPY
3844     if (appData.zippyPlay && newGame &&
3845         gameMode != IcsObserving && gameMode != IcsIdle &&
3846         gameMode != IcsExamining)
3847       ZippyFirstBoard(moveNum, basetime, increment);
3848 #endif
3849     
3850     /* Put the move on the move list, first converting
3851        to canonical algebraic form. */
3852     if (moveNum > 0) {
3853   if (appData.debugMode) {
3854     if (appData.debugMode) { int f = forwardMostMove;
3855         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3856                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3857     }
3858     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3859     fprintf(debugFP, "moveNum = %d\n", moveNum);
3860     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3861     setbuf(debugFP, NULL);
3862   }
3863         if (moveNum <= backwardMostMove) {
3864             /* We don't know what the board looked like before
3865                this move.  Punt. */
3866             strcpy(parseList[moveNum - 1], move_str);
3867             strcat(parseList[moveNum - 1], " ");
3868             strcat(parseList[moveNum - 1], elapsed_time);
3869             moveList[moveNum - 1][0] = NULLCHAR;
3870         } else if (strcmp(move_str, "none") == 0) {
3871             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3872             /* Again, we don't know what the board looked like;
3873                this is really the start of the game. */
3874             parseList[moveNum - 1][0] = NULLCHAR;
3875             moveList[moveNum - 1][0] = NULLCHAR;
3876             backwardMostMove = moveNum;
3877             startedFromSetupPosition = TRUE;
3878             fromX = fromY = toX = toY = -1;
3879         } else {
3880           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3881           //                 So we parse the long-algebraic move string in stead of the SAN move
3882           int valid; char buf[MSG_SIZ], *prom;
3883
3884           // str looks something like "Q/a1-a2"; kill the slash
3885           if(str[1] == '/') 
3886                 sprintf(buf, "%c%s", str[0], str+2);
3887           else  strcpy(buf, str); // might be castling
3888           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3889                 strcat(buf, prom); // long move lacks promo specification!
3890           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3891                 if(appData.debugMode) 
3892                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3893                 strcpy(move_str, buf);
3894           }
3895           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3896                                 &fromX, &fromY, &toX, &toY, &promoChar)
3897                || ParseOneMove(buf, moveNum - 1, &moveType,
3898                                 &fromX, &fromY, &toX, &toY, &promoChar);
3899           // end of long SAN patch
3900           if (valid) {
3901             (void) CoordsToAlgebraic(boards[moveNum - 1],
3902                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3903                                      fromY, fromX, toY, toX, promoChar,
3904                                      parseList[moveNum-1]);
3905             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3906                              castlingRights[moveNum]) ) {
3907               case MT_NONE:
3908               case MT_STALEMATE:
3909               default:
3910                 break;
3911               case MT_CHECK:
3912                 if(gameInfo.variant != VariantShogi)
3913                     strcat(parseList[moveNum - 1], "+");
3914                 break;
3915               case MT_CHECKMATE:
3916               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3917                 strcat(parseList[moveNum - 1], "#");
3918                 break;
3919             }
3920             strcat(parseList[moveNum - 1], " ");
3921             strcat(parseList[moveNum - 1], elapsed_time);
3922             /* currentMoveString is set as a side-effect of ParseOneMove */
3923             strcpy(moveList[moveNum - 1], currentMoveString);
3924             strcat(moveList[moveNum - 1], "\n");
3925           } else {
3926             /* Move from ICS was illegal!?  Punt. */
3927   if (appData.debugMode) {
3928     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3930   }
3931             strcpy(parseList[moveNum - 1], move_str);
3932             strcat(parseList[moveNum - 1], " ");
3933             strcat(parseList[moveNum - 1], elapsed_time);
3934             moveList[moveNum - 1][0] = NULLCHAR;
3935             fromX = fromY = toX = toY = -1;
3936           }
3937         }
3938   if (appData.debugMode) {
3939     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3940     setbuf(debugFP, NULL);
3941   }
3942
3943 #if ZIPPY
3944         /* Send move to chess program (BEFORE animating it). */
3945         if (appData.zippyPlay && !newGame && newMove && 
3946            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3947
3948             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3949                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3950                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3951                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3952                             move_str);
3953                     DisplayError(str, 0);
3954                 } else {
3955                     if (first.sendTime) {
3956                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3957                     }
3958                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3959                     if (firstMove && !bookHit) {
3960                         firstMove = FALSE;
3961                         if (first.useColors) {
3962                           SendToProgram(gameMode == IcsPlayingWhite ?
3963                                         "white\ngo\n" :
3964                                         "black\ngo\n", &first);
3965                         } else {
3966                           SendToProgram("go\n", &first);
3967                         }
3968                         first.maybeThinking = TRUE;
3969                     }
3970                 }
3971             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3972               if (moveList[moveNum - 1][0] == NULLCHAR) {
3973                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3974                 DisplayError(str, 0);
3975               } else {
3976                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3977                 SendMoveToProgram(moveNum - 1, &first);
3978               }
3979             }
3980         }
3981 #endif
3982     }
3983
3984     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3985         /* If move comes from a remote source, animate it.  If it
3986            isn't remote, it will have already been animated. */
3987         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3988             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3989         }
3990         if (!pausing && appData.highlightLastMove) {
3991             SetHighlights(fromX, fromY, toX, toY);
3992         }
3993     }
3994     
3995     /* Start the clocks */
3996     whiteFlag = blackFlag = FALSE;
3997     appData.clockMode = !(basetime == 0 && increment == 0);
3998     if (ticking == 0) {
3999       ics_clock_paused = TRUE;
4000       StopClocks();
4001     } else if (ticking == 1) {
4002       ics_clock_paused = FALSE;
4003     }
4004     if (gameMode == IcsIdle ||
4005         relation == RELATION_OBSERVING_STATIC ||
4006         relation == RELATION_EXAMINING ||
4007         ics_clock_paused)
4008       DisplayBothClocks();
4009     else
4010       StartClocks();
4011     
4012     /* Display opponents and material strengths */
4013     if (gameInfo.variant != VariantBughouse &&
4014         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4015         if (tinyLayout || smallLayout) {
4016             if(gameInfo.variant == VariantNormal)
4017                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4018                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4019                     basetime, increment);
4020             else
4021                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4022                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4023                     basetime, increment, (int) gameInfo.variant);
4024         } else {
4025             if(gameInfo.variant == VariantNormal)
4026                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4027                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4028                     basetime, increment);
4029             else
4030                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4031                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4032                     basetime, increment, VariantName(gameInfo.variant));
4033         }
4034         DisplayTitle(str);
4035   if (appData.debugMode) {
4036     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4037   }
4038     }
4039
4040    
4041     /* Display the board */
4042     if (!pausing && !appData.noGUI) {
4043       
4044       if (appData.premove)
4045           if (!gotPremove || 
4046              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4047              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4048               ClearPremoveHighlights();
4049
4050       DrawPosition(FALSE, boards[currentMove]);
4051       DisplayMove(moveNum - 1);
4052       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4053             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4054               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4055         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4056       }
4057     }
4058
4059     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4060 #if ZIPPY
4061     if(bookHit) { // [HGM] book: simulate book reply
4062         static char bookMove[MSG_SIZ]; // a bit generous?
4063
4064         programStats.nodes = programStats.depth = programStats.time = 
4065         programStats.score = programStats.got_only_move = 0;
4066         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4067
4068         strcpy(bookMove, "move ");
4069         strcat(bookMove, bookHit);
4070         HandleMachineMove(bookMove, &first);
4071     }
4072 #endif
4073 }
4074
4075 void
4076 GetMoveListEvent()
4077 {
4078     char buf[MSG_SIZ];
4079     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4080         ics_getting_history = H_REQUESTED;
4081         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4082         SendToICS(buf);
4083     }
4084 }
4085
4086 void
4087 AnalysisPeriodicEvent(force)
4088      int force;
4089 {
4090     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4091          && !force) || !appData.periodicUpdates)
4092       return;
4093
4094     /* Send . command to Crafty to collect stats */
4095     SendToProgram(".\n", &first);
4096
4097     /* Don't send another until we get a response (this makes
4098        us stop sending to old Crafty's which don't understand
4099        the "." command (sending illegal cmds resets node count & time,
4100        which looks bad)) */
4101     programStats.ok_to_send = 0;
4102 }
4103
4104 void ics_update_width(new_width)
4105         int new_width;
4106 {
4107         ics_printf("set width %d\n", new_width);
4108 }
4109
4110 void
4111 SendMoveToProgram(moveNum, cps)
4112      int moveNum;
4113      ChessProgramState *cps;
4114 {
4115     char buf[MSG_SIZ];
4116
4117     if (cps->useUsermove) {
4118       SendToProgram("usermove ", cps);
4119     }
4120     if (cps->useSAN) {
4121       char *space;
4122       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4123         int len = space - parseList[moveNum];
4124         memcpy(buf, parseList[moveNum], len);
4125         buf[len++] = '\n';
4126         buf[len] = NULLCHAR;
4127       } else {
4128         sprintf(buf, "%s\n", parseList[moveNum]);
4129       }
4130       SendToProgram(buf, cps);
4131     } else {
4132       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4133         AlphaRank(moveList[moveNum], 4);
4134         SendToProgram(moveList[moveNum], cps);
4135         AlphaRank(moveList[moveNum], 4); // and back
4136       } else
4137       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4138        * the engine. It would be nice to have a better way to identify castle 
4139        * moves here. */
4140       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4141                                                                          && cps->useOOCastle) {
4142         int fromX = moveList[moveNum][0] - AAA; 
4143         int fromY = moveList[moveNum][1] - ONE;
4144         int toX = moveList[moveNum][2] - AAA; 
4145         int toY = moveList[moveNum][3] - ONE;
4146         if((boards[moveNum][fromY][fromX] == WhiteKing 
4147             && boards[moveNum][toY][toX] == WhiteRook)
4148            || (boards[moveNum][fromY][fromX] == BlackKing 
4149                && boards[moveNum][toY][toX] == BlackRook)) {
4150           if(toX > fromX) SendToProgram("O-O\n", cps);
4151           else SendToProgram("O-O-O\n", cps);
4152         }
4153         else SendToProgram(moveList[moveNum], cps);
4154       }
4155       else SendToProgram(moveList[moveNum], cps);
4156       /* End of additions by Tord */
4157     }
4158
4159     /* [HGM] setting up the opening has brought engine in force mode! */
4160     /*       Send 'go' if we are in a mode where machine should play. */
4161     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4162         (gameMode == TwoMachinesPlay   ||
4163 #ifdef ZIPPY
4164          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4165 #endif
4166          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4167         SendToProgram("go\n", cps);
4168   if (appData.debugMode) {
4169     fprintf(debugFP, "(extra)\n");
4170   }
4171     }
4172     setboardSpoiledMachineBlack = 0;
4173 }
4174
4175 void
4176 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4177      ChessMove moveType;
4178      int fromX, fromY, toX, toY;
4179 {
4180     char user_move[MSG_SIZ];
4181
4182     switch (moveType) {
4183       default:
4184         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4185                 (int)moveType, fromX, fromY, toX, toY);
4186         DisplayError(user_move + strlen("say "), 0);
4187         break;
4188       case WhiteKingSideCastle:
4189       case BlackKingSideCastle:
4190       case WhiteQueenSideCastleWild:
4191       case BlackQueenSideCastleWild:
4192       /* PUSH Fabien */
4193       case WhiteHSideCastleFR:
4194       case BlackHSideCastleFR:
4195       /* POP Fabien */
4196         sprintf(user_move, "o-o\n");
4197         break;
4198       case WhiteQueenSideCastle:
4199       case BlackQueenSideCastle:
4200       case WhiteKingSideCastleWild:
4201       case BlackKingSideCastleWild:
4202       /* PUSH Fabien */
4203       case WhiteASideCastleFR:
4204       case BlackASideCastleFR:
4205       /* POP Fabien */
4206         sprintf(user_move, "o-o-o\n");
4207         break;
4208       case WhitePromotionQueen:
4209       case BlackPromotionQueen:
4210       case WhitePromotionRook:
4211       case BlackPromotionRook:
4212       case WhitePromotionBishop:
4213       case BlackPromotionBishop:
4214       case WhitePromotionKnight:
4215       case BlackPromotionKnight:
4216       case WhitePromotionKing:
4217       case BlackPromotionKing:
4218       case WhitePromotionChancellor:
4219       case BlackPromotionChancellor:
4220       case WhitePromotionArchbishop:
4221       case BlackPromotionArchbishop:
4222         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4223             sprintf(user_move, "%c%c%c%c=%c\n",
4224                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4225                 PieceToChar(WhiteFerz));
4226         else if(gameInfo.variant == VariantGreat)
4227             sprintf(user_move, "%c%c%c%c=%c\n",
4228                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4229                 PieceToChar(WhiteMan));
4230         else
4231             sprintf(user_move, "%c%c%c%c=%c\n",
4232                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4233                 PieceToChar(PromoPiece(moveType)));
4234         break;
4235       case WhiteDrop:
4236       case BlackDrop:
4237         sprintf(user_move, "%c@%c%c\n",
4238                 ToUpper(PieceToChar((ChessSquare) fromX)),
4239                 AAA + toX, ONE + toY);
4240         break;
4241       case NormalMove:
4242       case WhiteCapturesEnPassant:
4243       case BlackCapturesEnPassant:
4244       case IllegalMove:  /* could be a variant we don't quite understand */
4245         sprintf(user_move, "%c%c%c%c\n",
4246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4247         break;
4248     }
4249     SendToICS(user_move);
4250     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4251         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4252 }
4253
4254 void
4255 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4256      int rf, ff, rt, ft;
4257      char promoChar;
4258      char move[7];
4259 {
4260     if (rf == DROP_RANK) {
4261         sprintf(move, "%c@%c%c\n",
4262                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4263     } else {
4264         if (promoChar == 'x' || promoChar == NULLCHAR) {
4265             sprintf(move, "%c%c%c%c\n",
4266                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4267         } else {
4268             sprintf(move, "%c%c%c%c%c\n",
4269                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4270         }
4271     }
4272 }
4273
4274 void
4275 ProcessICSInitScript(f)
4276      FILE *f;
4277 {
4278     char buf[MSG_SIZ];
4279
4280     while (fgets(buf, MSG_SIZ, f)) {
4281         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4282     }
4283
4284     fclose(f);
4285 }
4286
4287
4288 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4289 void
4290 AlphaRank(char *move, int n)
4291 {
4292 //    char *p = move, c; int x, y;
4293
4294     if (appData.debugMode) {
4295         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4296     }
4297
4298     if(move[1]=='*' && 
4299        move[2]>='0' && move[2]<='9' &&
4300        move[3]>='a' && move[3]<='x'    ) {
4301         move[1] = '@';
4302         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4303         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4304     } else
4305     if(move[0]>='0' && move[0]<='9' &&
4306        move[1]>='a' && move[1]<='x' &&
4307        move[2]>='0' && move[2]<='9' &&
4308        move[3]>='a' && move[3]<='x'    ) {
4309         /* input move, Shogi -> normal */
4310         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4311         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4312         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4313         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4314     } else
4315     if(move[1]=='@' &&
4316        move[3]>='0' && move[3]<='9' &&
4317        move[2]>='a' && move[2]<='x'    ) {
4318         move[1] = '*';
4319         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4320         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4321     } else
4322     if(
4323        move[0]>='a' && move[0]<='x' &&
4324        move[3]>='0' && move[3]<='9' &&
4325        move[2]>='a' && move[2]<='x'    ) {
4326          /* output move, normal -> Shogi */
4327         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4328         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4329         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4330         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4331         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4332     }
4333     if (appData.debugMode) {
4334         fprintf(debugFP, "   out = '%s'\n", move);
4335     }
4336 }
4337
4338 /* Parser for moves from gnuchess, ICS, or user typein box */
4339 Boolean
4340 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4341      char *move;
4342      int moveNum;
4343      ChessMove *moveType;
4344      int *fromX, *fromY, *toX, *toY;
4345      char *promoChar;
4346 {       
4347     if (appData.debugMode) {
4348         fprintf(debugFP, "move to parse: %s\n", move);
4349     }
4350     *moveType = yylexstr(moveNum, move);
4351
4352     switch (*moveType) {
4353       case WhitePromotionChancellor:
4354       case BlackPromotionChancellor:
4355       case WhitePromotionArchbishop:
4356       case BlackPromotionArchbishop:
4357       case WhitePromotionQueen:
4358       case BlackPromotionQueen:
4359       case WhitePromotionRook:
4360       case BlackPromotionRook:
4361       case WhitePromotionBishop:
4362       case BlackPromotionBishop:
4363       case WhitePromotionKnight:
4364       case BlackPromotionKnight:
4365       case WhitePromotionKing:
4366       case BlackPromotionKing:
4367       case NormalMove:
4368       case WhiteCapturesEnPassant:
4369       case BlackCapturesEnPassant:
4370       case WhiteKingSideCastle:
4371       case WhiteQueenSideCastle:
4372       case BlackKingSideCastle:
4373       case BlackQueenSideCastle:
4374       case WhiteKingSideCastleWild:
4375       case WhiteQueenSideCastleWild:
4376       case BlackKingSideCastleWild:
4377       case BlackQueenSideCastleWild:
4378       /* Code added by Tord: */
4379       case WhiteHSideCastleFR:
4380       case WhiteASideCastleFR:
4381       case BlackHSideCastleFR:
4382       case BlackASideCastleFR:
4383       /* End of code added by Tord */
4384       case IllegalMove:         /* bug or odd chess variant */
4385         *fromX = currentMoveString[0] - AAA;
4386         *fromY = currentMoveString[1] - ONE;
4387         *toX = currentMoveString[2] - AAA;
4388         *toY = currentMoveString[3] - ONE;
4389         *promoChar = currentMoveString[4];
4390         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4391             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4392     if (appData.debugMode) {
4393         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4394     }
4395             *fromX = *fromY = *toX = *toY = 0;
4396             return FALSE;
4397         }
4398         if (appData.testLegality) {
4399           return (*moveType != IllegalMove);
4400         } else {
4401           return !(*fromX == *toX && *fromY == *toY);
4402         }
4403
4404       case WhiteDrop:
4405       case BlackDrop:
4406         *fromX = *moveType == WhiteDrop ?
4407           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4408           (int) CharToPiece(ToLower(currentMoveString[0]));
4409         *fromY = DROP_RANK;
4410         *toX = currentMoveString[2] - AAA;
4411         *toY = currentMoveString[3] - ONE;
4412         *promoChar = NULLCHAR;
4413         return TRUE;
4414
4415       case AmbiguousMove:
4416       case ImpossibleMove:
4417       case (ChessMove) 0:       /* end of file */
4418       case ElapsedTime:
4419       case Comment:
4420       case PGNTag:
4421       case NAG:
4422       case WhiteWins:
4423       case BlackWins:
4424       case GameIsDrawn:
4425       default:
4426     if (appData.debugMode) {
4427         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4428     }
4429         /* bug? */
4430         *fromX = *fromY = *toX = *toY = 0;
4431         *promoChar = NULLCHAR;
4432         return FALSE;
4433     }
4434 }
4435
4436 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4437 // All positions will have equal probability, but the current method will not provide a unique
4438 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4439 #define DARK 1
4440 #define LITE 2
4441 #define ANY 3
4442
4443 int squaresLeft[4];
4444 int piecesLeft[(int)BlackPawn];
4445 int seed, nrOfShuffles;
4446
4447 void GetPositionNumber()
4448 {       // sets global variable seed
4449         int i;
4450
4451         seed = appData.defaultFrcPosition;
4452         if(seed < 0) { // randomize based on time for negative FRC position numbers
4453                 for(i=0; i<50; i++) seed += random();
4454                 seed = random() ^ random() >> 8 ^ random() << 8;
4455                 if(seed<0) seed = -seed;
4456         }
4457 }
4458
4459 int put(Board board, int pieceType, int rank, int n, int shade)
4460 // put the piece on the (n-1)-th empty squares of the given shade
4461 {
4462         int i;
4463
4464         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4465                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4466                         board[rank][i] = (ChessSquare) pieceType;
4467                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4468                         squaresLeft[ANY]--;
4469                         piecesLeft[pieceType]--; 
4470                         return i;
4471                 }
4472         }
4473         return -1;
4474 }
4475
4476
4477 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4478 // calculate where the next piece goes, (any empty square), and put it there
4479 {
4480         int i;
4481
4482         i = seed % squaresLeft[shade];
4483         nrOfShuffles *= squaresLeft[shade];
4484         seed /= squaresLeft[shade];
4485         put(board, pieceType, rank, i, shade);
4486 }
4487
4488 void AddTwoPieces(Board board, int pieceType, int rank)
4489 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4490 {
4491         int i, n=squaresLeft[ANY], j=n-1, k;
4492
4493         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4494         i = seed % k;  // pick one
4495         nrOfShuffles *= k;
4496         seed /= k;
4497         while(i >= j) i -= j--;
4498         j = n - 1 - j; i += j;
4499         put(board, pieceType, rank, j, ANY);
4500         put(board, pieceType, rank, i, ANY);
4501 }
4502
4503 void SetUpShuffle(Board board, int number)
4504 {
4505         int i, p, first=1;
4506
4507         GetPositionNumber(); nrOfShuffles = 1;
4508
4509         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4510         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4511         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4512
4513         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4514
4515         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4516             p = (int) board[0][i];
4517             if(p < (int) BlackPawn) piecesLeft[p] ++;
4518             board[0][i] = EmptySquare;
4519         }
4520
4521         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4522             // shuffles restricted to allow normal castling put KRR first
4523             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4524                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4525             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4526                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4527             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4528                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4529             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4530                 put(board, WhiteRook, 0, 0, ANY);
4531             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4532         }
4533
4534         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4535             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4536             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4537                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4538                 while(piecesLeft[p] >= 2) {
4539                     AddOnePiece(board, p, 0, LITE);
4540                     AddOnePiece(board, p, 0, DARK);
4541                 }
4542                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4543             }
4544
4545         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4546             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4547             // but we leave King and Rooks for last, to possibly obey FRC restriction
4548             if(p == (int)WhiteRook) continue;
4549             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4550             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4551         }
4552
4553         // now everything is placed, except perhaps King (Unicorn) and Rooks
4554
4555         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4556             // Last King gets castling rights
4557             while(piecesLeft[(int)WhiteUnicorn]) {
4558                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4559                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4560             }
4561
4562             while(piecesLeft[(int)WhiteKing]) {
4563                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4564                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4565             }
4566
4567
4568         } else {
4569             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4570             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4571         }
4572
4573         // Only Rooks can be left; simply place them all
4574         while(piecesLeft[(int)WhiteRook]) {
4575                 i = put(board, WhiteRook, 0, 0, ANY);
4576                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4577                         if(first) {
4578                                 first=0;
4579                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4580                         }
4581                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4582                 }
4583         }
4584         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4585             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4586         }
4587
4588         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4589 }
4590
4591 int SetCharTable( char *table, const char * map )
4592 /* [HGM] moved here from winboard.c because of its general usefulness */
4593 /*       Basically a safe strcpy that uses the last character as King */
4594 {
4595     int result = FALSE; int NrPieces;
4596
4597     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4598                     && NrPieces >= 12 && !(NrPieces&1)) {
4599         int i; /* [HGM] Accept even length from 12 to 34 */
4600
4601         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4602         for( i=0; i<NrPieces/2-1; i++ ) {
4603             table[i] = map[i];
4604             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4605         }
4606         table[(int) WhiteKing]  = map[NrPieces/2-1];
4607         table[(int) BlackKing]  = map[NrPieces-1];
4608
4609         result = TRUE;
4610     }
4611
4612     return result;
4613 }
4614
4615 void Prelude(Board board)
4616 {       // [HGM] superchess: random selection of exo-pieces
4617         int i, j, k; ChessSquare p; 
4618         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4619
4620         GetPositionNumber(); // use FRC position number
4621
4622         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4623             SetCharTable(pieceToChar, appData.pieceToCharTable);
4624             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4625                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4626         }
4627
4628         j = seed%4;                 seed /= 4; 
4629         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4630         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4631         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4632         j = seed%3 + (seed%3 >= j); seed /= 3; 
4633         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4634         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4635         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4636         j = seed%3;                 seed /= 3; 
4637         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4638         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4639         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4640         j = seed%2 + (seed%2 >= j); seed /= 2; 
4641         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4642         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4643         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4644         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4645         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4646         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4647         put(board, exoPieces[0],    0, 0, ANY);
4648         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4649 }
4650
4651 void
4652 InitPosition(redraw)
4653      int redraw;
4654 {
4655     ChessSquare (* pieces)[BOARD_SIZE];
4656     int i, j, pawnRow, overrule,
4657     oldx = gameInfo.boardWidth,
4658     oldy = gameInfo.boardHeight,
4659     oldh = gameInfo.holdingsWidth,
4660     oldv = gameInfo.variant;
4661
4662     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4663
4664     /* [AS] Initialize pv info list [HGM] and game status */
4665     {
4666         for( i=0; i<MAX_MOVES; i++ ) {
4667             pvInfoList[i].depth = 0;
4668             epStatus[i]=EP_NONE;
4669             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4670         }
4671
4672         initialRulePlies = 0; /* 50-move counter start */
4673
4674         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4675         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4676     }
4677
4678     
4679     /* [HGM] logic here is completely changed. In stead of full positions */
4680     /* the initialized data only consist of the two backranks. The switch */
4681     /* selects which one we will use, which is than copied to the Board   */
4682     /* initialPosition, which for the rest is initialized by Pawns and    */
4683     /* empty squares. This initial position is then copied to boards[0],  */
4684     /* possibly after shuffling, so that it remains available.            */
4685
4686     gameInfo.holdingsWidth = 0; /* default board sizes */
4687     gameInfo.boardWidth    = 8;
4688     gameInfo.boardHeight   = 8;
4689     gameInfo.holdingsSize  = 0;
4690     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4691     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4692     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4693
4694     switch (gameInfo.variant) {
4695     case VariantFischeRandom:
4696       shuffleOpenings = TRUE;
4697     default:
4698       pieces = FIDEArray;
4699       break;
4700     case VariantShatranj:
4701       pieces = ShatranjArray;
4702       nrCastlingRights = 0;
4703       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4704       break;
4705     case VariantMakruk:
4706       pieces = makrukArray;
4707       nrCastlingRights = 0;
4708       startedFromSetupPosition = TRUE;
4709       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4710       break;
4711     case VariantTwoKings:
4712       pieces = twoKingsArray;
4713       break;
4714     case VariantCapaRandom:
4715       shuffleOpenings = TRUE;
4716     case VariantCapablanca:
4717       pieces = CapablancaArray;
4718       gameInfo.boardWidth = 10;
4719       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4720       break;
4721     case VariantGothic:
4722       pieces = GothicArray;
4723       gameInfo.boardWidth = 10;
4724       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4725       break;
4726     case VariantJanus:
4727       pieces = JanusArray;
4728       gameInfo.boardWidth = 10;
4729       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4730       nrCastlingRights = 6;
4731         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4732         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4733         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4734         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4735         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4736         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4737       break;
4738     case VariantFalcon:
4739       pieces = FalconArray;
4740       gameInfo.boardWidth = 10;
4741       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4742       break;
4743     case VariantXiangqi:
4744       pieces = XiangqiArray;
4745       gameInfo.boardWidth  = 9;
4746       gameInfo.boardHeight = 10;
4747       nrCastlingRights = 0;
4748       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4749       break;
4750     case VariantShogi:
4751       pieces = ShogiArray;
4752       gameInfo.boardWidth  = 9;
4753       gameInfo.boardHeight = 9;
4754       gameInfo.holdingsSize = 7;
4755       nrCastlingRights = 0;
4756       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4757       break;
4758     case VariantCourier:
4759       pieces = CourierArray;
4760       gameInfo.boardWidth  = 12;
4761       nrCastlingRights = 0;
4762       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4763       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4764       break;
4765     case VariantKnightmate:
4766       pieces = KnightmateArray;
4767       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4768       break;
4769     case VariantFairy:
4770       pieces = fairyArray;
4771       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4772       break;
4773     case VariantGreat:
4774       pieces = GreatArray;
4775       gameInfo.boardWidth = 10;
4776       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4777       gameInfo.holdingsSize = 8;
4778       break;
4779     case VariantSuper:
4780       pieces = FIDEArray;
4781       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4782       gameInfo.holdingsSize = 8;
4783       startedFromSetupPosition = TRUE;
4784       break;
4785     case VariantCrazyhouse:
4786     case VariantBughouse:
4787       pieces = FIDEArray;
4788       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4789       gameInfo.holdingsSize = 5;
4790       break;
4791     case VariantWildCastle:
4792       pieces = FIDEArray;
4793       /* !!?shuffle with kings guaranteed to be on d or e file */
4794       shuffleOpenings = 1;
4795       break;
4796     case VariantNoCastle:
4797       pieces = FIDEArray;
4798       nrCastlingRights = 0;
4799       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4800       /* !!?unconstrained back-rank shuffle */
4801       shuffleOpenings = 1;
4802       break;
4803     }
4804
4805     overrule = 0;
4806     if(appData.NrFiles >= 0) {
4807         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4808         gameInfo.boardWidth = appData.NrFiles;
4809     }
4810     if(appData.NrRanks >= 0) {
4811         gameInfo.boardHeight = appData.NrRanks;
4812     }
4813     if(appData.holdingsSize >= 0) {
4814         i = appData.holdingsSize;
4815         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4816         gameInfo.holdingsSize = i;
4817     }
4818     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4819     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4820         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4821
4822     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4823     if(pawnRow < 1) pawnRow = 1;
4824     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4825
4826     /* User pieceToChar list overrules defaults */
4827     if(appData.pieceToCharTable != NULL)
4828         SetCharTable(pieceToChar, appData.pieceToCharTable);
4829
4830     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4831
4832         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4833             s = (ChessSquare) 0; /* account holding counts in guard band */
4834         for( i=0; i<BOARD_HEIGHT; i++ )
4835             initialPosition[i][j] = s;
4836
4837         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4838         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4839         initialPosition[pawnRow][j] = WhitePawn;
4840         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4841         if(gameInfo.variant == VariantXiangqi) {
4842             if(j&1) {
4843                 initialPosition[pawnRow][j] = 
4844                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4845                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4846                    initialPosition[2][j] = WhiteCannon;
4847                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4848                 }
4849             }
4850         }
4851         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4852     }
4853     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4854
4855             j=BOARD_LEFT+1;
4856             initialPosition[1][j] = WhiteBishop;
4857             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4858             j=BOARD_RGHT-2;
4859             initialPosition[1][j] = WhiteRook;
4860             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4861     }
4862
4863     if( nrCastlingRights == -1) {
4864         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4865         /*       This sets default castling rights from none to normal corners   */
4866         /* Variants with other castling rights must set them themselves above    */
4867         nrCastlingRights = 6;
4868        
4869         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4870         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4871         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4872         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4873         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4874         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4875      }
4876
4877      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4878      if(gameInfo.variant == VariantGreat) { // promotion commoners
4879         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4880         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4881         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4882         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4883      }
4884   if (appData.debugMode) {
4885     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4886   }
4887     if(shuffleOpenings) {
4888         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4889         startedFromSetupPosition = TRUE;
4890     }
4891     if(startedFromPositionFile) {
4892       /* [HGM] loadPos: use PositionFile for every new game */
4893       CopyBoard(initialPosition, filePosition);
4894       for(i=0; i<nrCastlingRights; i++)
4895           castlingRights[0][i] = initialRights[i] = fileRights[i];
4896       startedFromSetupPosition = TRUE;
4897     }
4898
4899     CopyBoard(boards[0], initialPosition);
4900
4901     if(oldx != gameInfo.boardWidth ||
4902        oldy != gameInfo.boardHeight ||
4903        oldh != gameInfo.holdingsWidth
4904 #ifdef GOTHIC
4905        || oldv == VariantGothic ||        // For licensing popups
4906        gameInfo.variant == VariantGothic
4907 #endif
4908 #ifdef FALCON
4909        || oldv == VariantFalcon ||
4910        gameInfo.variant == VariantFalcon
4911 #endif
4912                                          )
4913             InitDrawingSizes(-2 ,0);
4914
4915     if (redraw)
4916       DrawPosition(TRUE, boards[currentMove]);
4917 }
4918
4919 void
4920 SendBoard(cps, moveNum)
4921      ChessProgramState *cps;
4922      int moveNum;
4923 {
4924     char message[MSG_SIZ];
4925     
4926     if (cps->useSetboard) {
4927       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4928       sprintf(message, "setboard %s\n", fen);
4929       SendToProgram(message, cps);
4930       free(fen);
4931
4932     } else {
4933       ChessSquare *bp;
4934       int i, j;
4935       /* Kludge to set black to move, avoiding the troublesome and now
4936        * deprecated "black" command.
4937        */
4938       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4939
4940       SendToProgram("edit\n", cps);
4941       SendToProgram("#\n", cps);
4942       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4943         bp = &boards[moveNum][i][BOARD_LEFT];
4944         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4945           if ((int) *bp < (int) BlackPawn) {
4946             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4947                     AAA + j, ONE + i);
4948             if(message[0] == '+' || message[0] == '~') {
4949                 sprintf(message, "%c%c%c+\n",
4950                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4951                         AAA + j, ONE + i);
4952             }
4953             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4954                 message[1] = BOARD_RGHT   - 1 - j + '1';
4955                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4956             }
4957             SendToProgram(message, cps);
4958           }
4959         }
4960       }
4961     
4962       SendToProgram("c\n", cps);
4963       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4964         bp = &boards[moveNum][i][BOARD_LEFT];
4965         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4966           if (((int) *bp != (int) EmptySquare)
4967               && ((int) *bp >= (int) BlackPawn)) {
4968             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4969                     AAA + j, ONE + i);
4970             if(message[0] == '+' || message[0] == '~') {
4971                 sprintf(message, "%c%c%c+\n",
4972                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4973                         AAA + j, ONE + i);
4974             }
4975             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4976                 message[1] = BOARD_RGHT   - 1 - j + '1';
4977                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4978             }
4979             SendToProgram(message, cps);
4980           }
4981         }
4982       }
4983     
4984       SendToProgram(".\n", cps);
4985     }
4986     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4987 }
4988
4989 int
4990 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4991 {
4992     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4993     /* [HGM] add Shogi promotions */
4994     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4995     ChessSquare piece;
4996     ChessMove moveType;
4997     Boolean premove;
4998
4999     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5000     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5001
5002     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5003       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5004         return FALSE;
5005
5006     piece = boards[currentMove][fromY][fromX];
5007     if(gameInfo.variant == VariantShogi) {
5008         promotionZoneSize = 3;
5009         highestPromotingPiece = (int)WhiteFerz;
5010     } else if(gameInfo.variant == VariantMakruk) {
5011         promotionZoneSize = 3;
5012     }
5013
5014     // next weed out all moves that do not touch the promotion zone at all
5015     if((int)piece >= BlackPawn) {
5016         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5017              return FALSE;
5018         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5019     } else {
5020         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5021            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5022     }
5023
5024     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5025
5026     // weed out mandatory Shogi promotions
5027     if(gameInfo.variant == VariantShogi) {
5028         if(piece >= BlackPawn) {
5029             if(toY == 0 && piece == BlackPawn ||
5030                toY == 0 && piece == BlackQueen ||
5031                toY <= 1 && piece == BlackKnight) {
5032                 *promoChoice = '+';
5033                 return FALSE;
5034             }
5035         } else {
5036             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5037                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5038                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5039                 *promoChoice = '+';
5040                 return FALSE;
5041             }
5042         }
5043     }
5044
5045     // weed out obviously illegal Pawn moves
5046     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5047         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5048         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5049         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5050         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5051         // note we are not allowed to test for valid (non-)capture, due to premove
5052     }
5053
5054     // we either have a choice what to promote to, or (in Shogi) whether to promote
5055     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5056         *promoChoice = PieceToChar(BlackFerz);  // no choice
5057         return FALSE;
5058     }
5059     if(appData.alwaysPromoteToQueen) { // predetermined
5060         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5061              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5062         else *promoChoice = PieceToChar(BlackQueen);
5063         return FALSE;
5064     }
5065
5066     // suppress promotion popup on illegal moves that are not premoves
5067     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5068               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5069     if(appData.testLegality && !premove) {
5070         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5071                         epStatus[currentMove], castlingRights[currentMove],
5072                         fromY, fromX, toY, toX, NULLCHAR);
5073         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5074            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5075             return FALSE;
5076     }
5077
5078     return TRUE;
5079 }
5080
5081 int
5082 InPalace(row, column)
5083      int row, column;
5084 {   /* [HGM] for Xiangqi */
5085     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5086          column < (BOARD_WIDTH + 4)/2 &&
5087          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5088     return FALSE;
5089 }
5090
5091 int
5092 PieceForSquare (x, y)
5093      int x;
5094      int y;
5095 {
5096   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5097      return -1;
5098   else
5099      return boards[currentMove][y][x];
5100 }
5101
5102 int
5103 OKToStartUserMove(x, y)
5104      int x, y;
5105 {
5106     ChessSquare from_piece;
5107     int white_piece;
5108
5109     if (matchMode) return FALSE;
5110     if (gameMode == EditPosition) return TRUE;
5111
5112     if (x >= 0 && y >= 0)
5113       from_piece = boards[currentMove][y][x];
5114     else
5115       from_piece = EmptySquare;
5116
5117     if (from_piece == EmptySquare) return FALSE;
5118
5119     white_piece = (int)from_piece >= (int)WhitePawn &&
5120       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5121
5122     switch (gameMode) {
5123       case PlayFromGameFile:
5124       case AnalyzeFile:
5125       case TwoMachinesPlay:
5126       case EndOfGame:
5127         return FALSE;
5128
5129       case IcsObserving:
5130       case IcsIdle:
5131         return FALSE;
5132
5133       case MachinePlaysWhite:
5134       case IcsPlayingBlack:
5135         if (appData.zippyPlay) return FALSE;
5136         if (white_piece) {
5137             DisplayMoveError(_("You are playing Black"));
5138             return FALSE;
5139         }
5140         break;
5141
5142       case MachinePlaysBlack:
5143       case IcsPlayingWhite:
5144         if (appData.zippyPlay) return FALSE;
5145         if (!white_piece) {
5146             DisplayMoveError(_("You are playing White"));
5147             return FALSE;
5148         }
5149         break;
5150
5151       case EditGame:
5152         if (!white_piece && WhiteOnMove(currentMove)) {
5153             DisplayMoveError(_("It is White's turn"));
5154             return FALSE;
5155         }           
5156         if (white_piece && !WhiteOnMove(currentMove)) {
5157             DisplayMoveError(_("It is Black's turn"));
5158             return FALSE;
5159         }           
5160         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5161             /* Editing correspondence game history */
5162             /* Could disallow this or prompt for confirmation */
5163             cmailOldMove = -1;
5164         }
5165         if (currentMove < forwardMostMove) {
5166             /* Discarding moves */
5167             /* Could prompt for confirmation here,
5168                but I don't think that's such a good idea */
5169             forwardMostMove = currentMove;
5170         }
5171         break;
5172
5173       case BeginningOfGame:
5174         if (appData.icsActive) return FALSE;
5175         if (!appData.noChessProgram) {
5176             if (!white_piece) {
5177                 DisplayMoveError(_("You are playing White"));
5178                 return FALSE;
5179             }
5180         }
5181         break;
5182         
5183       case Training:
5184         if (!white_piece && WhiteOnMove(currentMove)) {
5185             DisplayMoveError(_("It is White's turn"));
5186             return FALSE;
5187         }           
5188         if (white_piece && !WhiteOnMove(currentMove)) {
5189             DisplayMoveError(_("It is Black's turn"));
5190             return FALSE;
5191         }           
5192         break;
5193
5194       default:
5195       case IcsExamining:
5196         break;
5197     }
5198     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5199         && gameMode != AnalyzeFile && gameMode != Training) {
5200         DisplayMoveError(_("Displayed position is not current"));
5201         return FALSE;
5202     }
5203     return TRUE;
5204 }
5205
5206 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5207 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5208 int lastLoadGameUseList = FALSE;
5209 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5210 ChessMove lastLoadGameStart = (ChessMove) 0;
5211
5212 ChessMove
5213 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5214      int fromX, fromY, toX, toY;
5215      int promoChar;
5216      Boolean captureOwn;
5217 {
5218     ChessMove moveType;
5219     ChessSquare pdown, pup;
5220
5221     /* Check if the user is playing in turn.  This is complicated because we
5222        let the user "pick up" a piece before it is his turn.  So the piece he
5223        tried to pick up may have been captured by the time he puts it down!
5224        Therefore we use the color the user is supposed to be playing in this
5225        test, not the color of the piece that is currently on the starting
5226        square---except in EditGame mode, where the user is playing both
5227        sides; fortunately there the capture race can't happen.  (It can
5228        now happen in IcsExamining mode, but that's just too bad.  The user
5229        will get a somewhat confusing message in that case.)
5230        */
5231
5232     switch (gameMode) {
5233       case PlayFromGameFile:
5234       case AnalyzeFile:
5235       case TwoMachinesPlay:
5236       case EndOfGame:
5237       case IcsObserving:
5238       case IcsIdle:
5239         /* We switched into a game mode where moves are not accepted,
5240            perhaps while the mouse button was down. */
5241         return ImpossibleMove;
5242
5243       case MachinePlaysWhite:
5244         /* User is moving for Black */
5245         if (WhiteOnMove(currentMove)) {
5246             DisplayMoveError(_("It is White's turn"));
5247             return ImpossibleMove;
5248         }
5249         break;
5250
5251       case MachinePlaysBlack:
5252         /* User is moving for White */
5253         if (!WhiteOnMove(currentMove)) {
5254             DisplayMoveError(_("It is Black's turn"));
5255             return ImpossibleMove;
5256         }
5257         break;
5258
5259       case EditGame:
5260       case IcsExamining:
5261       case BeginningOfGame:
5262       case AnalyzeMode:
5263       case Training:
5264         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5265             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5266             /* User is moving for Black */
5267             if (WhiteOnMove(currentMove)) {
5268                 DisplayMoveError(_("It is White's turn"));
5269                 return ImpossibleMove;
5270             }
5271         } else {
5272             /* User is moving for White */
5273             if (!WhiteOnMove(currentMove)) {
5274                 DisplayMoveError(_("It is Black's turn"));
5275                 return ImpossibleMove;
5276             }
5277         }
5278         break;
5279
5280       case IcsPlayingBlack:
5281         /* User is moving for Black */
5282         if (WhiteOnMove(currentMove)) {
5283             if (!appData.premove) {
5284                 DisplayMoveError(_("It is White's turn"));
5285             } else if (toX >= 0 && toY >= 0) {
5286                 premoveToX = toX;
5287                 premoveToY = toY;
5288                 premoveFromX = fromX;
5289                 premoveFromY = fromY;
5290                 premovePromoChar = promoChar;
5291                 gotPremove = 1;
5292                 if (appData.debugMode) 
5293                     fprintf(debugFP, "Got premove: fromX %d,"
5294                             "fromY %d, toX %d, toY %d\n",
5295                             fromX, fromY, toX, toY);
5296             }
5297             return ImpossibleMove;
5298         }
5299         break;
5300
5301       case IcsPlayingWhite:
5302         /* User is moving for White */
5303         if (!WhiteOnMove(currentMove)) {
5304             if (!appData.premove) {
5305                 DisplayMoveError(_("It is Black's turn"));
5306             } else if (toX >= 0 && toY >= 0) {
5307                 premoveToX = toX;
5308                 premoveToY = toY;
5309                 premoveFromX = fromX;
5310                 premoveFromY = fromY;
5311                 premovePromoChar = promoChar;
5312                 gotPremove = 1;
5313                 if (appData.debugMode) 
5314                     fprintf(debugFP, "Got premove: fromX %d,"
5315                             "fromY %d, toX %d, toY %d\n",
5316                             fromX, fromY, toX, toY);
5317             }
5318             return ImpossibleMove;
5319         }
5320         break;
5321
5322       default:
5323         break;
5324
5325       case EditPosition:
5326         /* EditPosition, empty square, or different color piece;
5327            click-click move is possible */
5328         if (toX == -2 || toY == -2) {
5329             boards[0][fromY][fromX] = EmptySquare;
5330             return AmbiguousMove;
5331         } else if (toX >= 0 && toY >= 0) {
5332             boards[0][toY][toX] = boards[0][fromY][fromX];
5333             boards[0][fromY][fromX] = EmptySquare;
5334             return AmbiguousMove;
5335         }
5336         return ImpossibleMove;
5337     }
5338
5339     if(toX < 0 || toY < 0) return ImpossibleMove;
5340     pdown = boards[currentMove][fromY][fromX];
5341     pup = boards[currentMove][toY][toX];
5342
5343     /* [HGM] If move started in holdings, it means a drop */
5344     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5345          if( pup != EmptySquare ) return ImpossibleMove;
5346          if(appData.testLegality) {
5347              /* it would be more logical if LegalityTest() also figured out
5348               * which drops are legal. For now we forbid pawns on back rank.
5349               * Shogi is on its own here...
5350               */
5351              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5352                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5353                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5354          }
5355          return WhiteDrop; /* Not needed to specify white or black yet */
5356     }
5357
5358     userOfferedDraw = FALSE;
5359         
5360     /* [HGM] always test for legality, to get promotion info */
5361     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5362                           epStatus[currentMove], castlingRights[currentMove],
5363                                          fromY, fromX, toY, toX, promoChar);
5364     /* [HGM] but possibly ignore an IllegalMove result */
5365     if (appData.testLegality) {
5366         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5367             DisplayMoveError(_("Illegal move"));
5368             return ImpossibleMove;
5369         }
5370     }
5371
5372     return moveType;
5373     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5374        function is made into one that returns an OK move type if FinishMove
5375        should be called. This to give the calling driver routine the
5376        opportunity to finish the userMove input with a promotion popup,
5377        without bothering the user with this for invalid or illegal moves */
5378
5379 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5380 }
5381
5382 /* Common tail of UserMoveEvent and DropMenuEvent */
5383 int
5384 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5385      ChessMove moveType;
5386      int fromX, fromY, toX, toY;
5387      /*char*/int promoChar;
5388 {
5389     char *bookHit = 0;
5390
5391     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5392         // [HGM] superchess: suppress promotions to non-available piece
5393         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5394         if(WhiteOnMove(currentMove)) {
5395             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5396         } else {
5397             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5398         }
5399     }
5400
5401     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5402        move type in caller when we know the move is a legal promotion */
5403     if(moveType == NormalMove && promoChar)
5404         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5405
5406     /* [HGM] convert drag-and-drop piece drops to standard form */
5407     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5408          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5409            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5410                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5411            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5412            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5413            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5414            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5415          fromY = DROP_RANK;
5416     }
5417
5418     /* [HGM] <popupFix> The following if has been moved here from
5419        UserMoveEvent(). Because it seemed to belong here (why not allow
5420        piece drops in training games?), and because it can only be
5421        performed after it is known to what we promote. */
5422     if (gameMode == Training) {
5423       /* compare the move played on the board to the next move in the
5424        * game. If they match, display the move and the opponent's response. 
5425        * If they don't match, display an error message.
5426        */
5427       int saveAnimate;
5428       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5429       CopyBoard(testBoard, boards[currentMove]);
5430       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5431
5432       if (CompareBoards(testBoard, boards[currentMove+1])) {
5433         ForwardInner(currentMove+1);
5434
5435         /* Autoplay the opponent's response.
5436          * if appData.animate was TRUE when Training mode was entered,
5437          * the response will be animated.
5438          */
5439         saveAnimate = appData.animate;
5440         appData.animate = animateTraining;
5441         ForwardInner(currentMove+1);
5442         appData.animate = saveAnimate;
5443
5444         /* check for the end of the game */
5445         if (currentMove >= forwardMostMove) {
5446           gameMode = PlayFromGameFile;
5447           ModeHighlight();
5448           SetTrainingModeOff();
5449           DisplayInformation(_("End of game"));
5450         }
5451       } else {
5452         DisplayError(_("Incorrect move"), 0);
5453       }
5454       return 1;
5455     }
5456
5457   /* Ok, now we know that the move is good, so we can kill
5458      the previous line in Analysis Mode */
5459   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5460     forwardMostMove = currentMove;
5461   }
5462
5463   /* If we need the chess program but it's dead, restart it */
5464   ResurrectChessProgram();
5465
5466   /* A user move restarts a paused game*/
5467   if (pausing)
5468     PauseEvent();
5469
5470   thinkOutput[0] = NULLCHAR;
5471
5472   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5473
5474   if (gameMode == BeginningOfGame) {
5475     if (appData.noChessProgram) {
5476       gameMode = EditGame;
5477       SetGameInfo();
5478     } else {
5479       char buf[MSG_SIZ];
5480       gameMode = MachinePlaysBlack;
5481       StartClocks();
5482       SetGameInfo();
5483       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5484       DisplayTitle(buf);
5485       if (first.sendName) {
5486         sprintf(buf, "name %s\n", gameInfo.white);
5487         SendToProgram(buf, &first);
5488       }
5489       StartClocks();
5490     }
5491     ModeHighlight();
5492   }
5493
5494   /* Relay move to ICS or chess engine */
5495   if (appData.icsActive) {
5496     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5497         gameMode == IcsExamining) {
5498       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5499       ics_user_moved = 1;
5500     }
5501   } else {
5502     if (first.sendTime && (gameMode == BeginningOfGame ||
5503                            gameMode == MachinePlaysWhite ||
5504                            gameMode == MachinePlaysBlack)) {
5505       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5506     }
5507     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5508          // [HGM] book: if program might be playing, let it use book
5509         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5510         first.maybeThinking = TRUE;
5511     } else SendMoveToProgram(forwardMostMove-1, &first);
5512     if (currentMove == cmailOldMove + 1) {
5513       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5514     }
5515   }
5516
5517   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5518
5519   switch (gameMode) {
5520   case EditGame:
5521     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5522                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5523     case MT_NONE:
5524     case MT_CHECK:
5525       break;
5526     case MT_CHECKMATE:
5527     case MT_STAINMATE:
5528       if (WhiteOnMove(currentMove)) {
5529         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5530       } else {
5531         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5532       }
5533       break;
5534     case MT_STALEMATE:
5535       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5536       break;
5537     }
5538     break;
5539     
5540   case MachinePlaysBlack:
5541   case MachinePlaysWhite:
5542     /* disable certain menu options while machine is thinking */
5543     SetMachineThinkingEnables();
5544     break;
5545
5546   default:
5547     break;
5548   }
5549
5550   if(bookHit) { // [HGM] book: simulate book reply
5551         static char bookMove[MSG_SIZ]; // a bit generous?
5552
5553         programStats.nodes = programStats.depth = programStats.time = 
5554         programStats.score = programStats.got_only_move = 0;
5555         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5556
5557         strcpy(bookMove, "move ");
5558         strcat(bookMove, bookHit);
5559         HandleMachineMove(bookMove, &first);
5560   }
5561   return 1;
5562 }
5563
5564 void
5565 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5566      int fromX, fromY, toX, toY;
5567      int promoChar;
5568 {
5569     /* [HGM] This routine was added to allow calling of its two logical
5570        parts from other modules in the old way. Before, UserMoveEvent()
5571        automatically called FinishMove() if the move was OK, and returned
5572        otherwise. I separated the two, in order to make it possible to
5573        slip a promotion popup in between. But that it always needs two
5574        calls, to the first part, (now called UserMoveTest() ), and to
5575        FinishMove if the first part succeeded. Calls that do not need
5576        to do anything in between, can call this routine the old way. 
5577     */
5578     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5579 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5580     if(moveType == AmbiguousMove)
5581         DrawPosition(FALSE, boards[currentMove]);
5582     else if(moveType != ImpossibleMove && moveType != Comment)
5583         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5584 }
5585
5586 void LeftClick(ClickType clickType, int xPix, int yPix)
5587 {
5588     int x, y;
5589     Boolean saveAnimate;
5590     static int second = 0, promotionChoice = 0;
5591     char promoChoice = NULLCHAR;
5592
5593     if (clickType == Press) ErrorPopDown();
5594
5595     x = EventToSquare(xPix, BOARD_WIDTH);
5596     y = EventToSquare(yPix, BOARD_HEIGHT);
5597     if (!flipView && y >= 0) {
5598         y = BOARD_HEIGHT - 1 - y;
5599     }
5600     if (flipView && x >= 0) {
5601         x = BOARD_WIDTH - 1 - x;
5602     }
5603
5604     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5605         if(clickType == Release) return; // ignore upclick of click-click destination
5606         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5607         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5608         if(gameInfo.holdingsWidth && 
5609                 (WhiteOnMove(currentMove) 
5610                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5611                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5612             // click in right holdings, for determining promotion piece
5613             ChessSquare p = boards[currentMove][y][x];
5614             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5615             if(p != EmptySquare) {
5616                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5617                 fromX = fromY = -1;
5618                 return;
5619             }
5620         }
5621         DrawPosition(FALSE, boards[currentMove]);
5622         return;
5623     }
5624
5625     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5626     if(clickType == Press
5627             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5628               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5629               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5630         return;
5631
5632     if (fromX == -1) {
5633         if (clickType == Press) {
5634             /* First square */
5635             if (OKToStartUserMove(x, y)) {
5636                 fromX = x;
5637                 fromY = y;
5638                 second = 0;
5639                 DragPieceBegin(xPix, yPix);
5640                 if (appData.highlightDragging) {
5641                     SetHighlights(x, y, -1, -1);
5642                 }
5643             }
5644         }
5645         return;
5646     }
5647
5648     /* fromX != -1 */
5649     if (clickType == Press && gameMode != EditPosition) {
5650         ChessSquare fromP;
5651         ChessSquare toP;
5652         int frc;
5653
5654         // ignore off-board to clicks
5655         if(y < 0 || x < 0) return;
5656
5657         /* Check if clicking again on the same color piece */
5658         fromP = boards[currentMove][fromY][fromX];
5659         toP = boards[currentMove][y][x];
5660         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5661         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5662              WhitePawn <= toP && toP <= WhiteKing &&
5663              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5664              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5665             (BlackPawn <= fromP && fromP <= BlackKing && 
5666              BlackPawn <= toP && toP <= BlackKing &&
5667              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5668              !(fromP == BlackKing && toP == BlackRook && frc))) {
5669             /* Clicked again on same color piece -- changed his mind */
5670             second = (x == fromX && y == fromY);
5671             if (appData.highlightDragging) {
5672                 SetHighlights(x, y, -1, -1);
5673             } else {
5674                 ClearHighlights();
5675             }
5676             if (OKToStartUserMove(x, y)) {
5677                 fromX = x;
5678                 fromY = y;
5679                 DragPieceBegin(xPix, yPix);
5680             }
5681             return;
5682         }
5683         // ignore clicks on holdings
5684         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5685     }
5686
5687     if (clickType == Release && x == fromX && y == fromY) {
5688         DragPieceEnd(xPix, yPix);
5689         if (appData.animateDragging) {
5690             /* Undo animation damage if any */
5691             DrawPosition(FALSE, NULL);
5692         }
5693         if (second) {
5694             /* Second up/down in same square; just abort move */
5695             second = 0;
5696             fromX = fromY = -1;
5697             ClearHighlights();
5698             gotPremove = 0;
5699             ClearPremoveHighlights();
5700         } else {
5701             /* First upclick in same square; start click-click mode */
5702             SetHighlights(x, y, -1, -1);
5703         }
5704         return;
5705     }
5706
5707     /* we now have a different from- and (possibly off-board) to-square */
5708     /* Completed move */
5709     toX = x;
5710     toY = y;
5711     saveAnimate = appData.animate;
5712     if (clickType == Press) {
5713         /* Finish clickclick move */
5714         if (appData.animate || appData.highlightLastMove) {
5715             SetHighlights(fromX, fromY, toX, toY);
5716         } else {
5717             ClearHighlights();
5718         }
5719     } else {
5720         /* Finish drag move */
5721         if (appData.highlightLastMove) {
5722             SetHighlights(fromX, fromY, toX, toY);
5723         } else {
5724             ClearHighlights();
5725         }
5726         DragPieceEnd(xPix, yPix);
5727         /* Don't animate move and drag both */
5728         appData.animate = FALSE;
5729     }
5730
5731     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5732     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5733         ClearHighlights();
5734         fromX = fromY = -1;
5735         DrawPosition(TRUE, NULL);
5736         return;
5737     }
5738
5739     // off-board moves should not be highlighted
5740     if(x < 0 || x < 0) ClearHighlights();
5741
5742     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5743         SetHighlights(fromX, fromY, toX, toY);
5744         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5745             // [HGM] super: promotion to captured piece selected from holdings
5746             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5747             promotionChoice = TRUE;
5748             // kludge follows to temporarily execute move on display, without promoting yet
5749             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5750             boards[currentMove][toY][toX] = p;
5751             DrawPosition(FALSE, boards[currentMove]);
5752             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5753             boards[currentMove][toY][toX] = q;
5754             DisplayMessage("Click in holdings to choose piece", "");
5755             return;
5756         }
5757         PromotionPopUp();
5758     } else {
5759         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5760         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5761         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5762         fromX = fromY = -1;
5763     }
5764     appData.animate = saveAnimate;
5765     if (appData.animate || appData.animateDragging) {
5766         /* Undo animation damage if needed */
5767         DrawPosition(FALSE, NULL);
5768     }
5769 }
5770
5771 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5772 {
5773 //    char * hint = lastHint;
5774     FrontEndProgramStats stats;
5775
5776     stats.which = cps == &first ? 0 : 1;
5777     stats.depth = cpstats->depth;
5778     stats.nodes = cpstats->nodes;
5779     stats.score = cpstats->score;
5780     stats.time = cpstats->time;
5781     stats.pv = cpstats->movelist;
5782     stats.hint = lastHint;
5783     stats.an_move_index = 0;
5784     stats.an_move_count = 0;
5785
5786     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5787         stats.hint = cpstats->move_name;
5788         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5789         stats.an_move_count = cpstats->nr_moves;
5790     }
5791
5792     SetProgramStats( &stats );
5793 }
5794
5795 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5796 {   // [HGM] book: this routine intercepts moves to simulate book replies
5797     char *bookHit = NULL;
5798
5799     //first determine if the incoming move brings opponent into his book
5800     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5801         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5802     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5803     if(bookHit != NULL && !cps->bookSuspend) {
5804         // make sure opponent is not going to reply after receiving move to book position
5805         SendToProgram("force\n", cps);
5806         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5807     }
5808     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5809     // now arrange restart after book miss
5810     if(bookHit) {
5811         // after a book hit we never send 'go', and the code after the call to this routine
5812         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5813         char buf[MSG_SIZ];
5814         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5815         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5816         SendToProgram(buf, cps);
5817         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5818     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5819         SendToProgram("go\n", cps);
5820         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5821     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5822         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5823             SendToProgram("go\n", cps); 
5824         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5825     }
5826     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5827 }
5828
5829 char *savedMessage;
5830 ChessProgramState *savedState;
5831 void DeferredBookMove(void)
5832 {
5833         if(savedState->lastPing != savedState->lastPong)
5834                     ScheduleDelayedEvent(DeferredBookMove, 10);
5835         else
5836         HandleMachineMove(savedMessage, savedState);
5837 }
5838
5839 void
5840 HandleMachineMove(message, cps)
5841      char *message;
5842      ChessProgramState *cps;
5843 {
5844     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5845     char realname[MSG_SIZ];
5846     int fromX, fromY, toX, toY;
5847     ChessMove moveType;
5848     char promoChar;
5849     char *p;
5850     int machineWhite;
5851     char *bookHit;
5852
5853 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5854     /*
5855      * Kludge to ignore BEL characters
5856      */
5857     while (*message == '\007') message++;
5858
5859     /*
5860      * [HGM] engine debug message: ignore lines starting with '#' character
5861      */
5862     if(cps->debug && *message == '#') return;
5863
5864     /*
5865      * Look for book output
5866      */
5867     if (cps == &first && bookRequested) {
5868         if (message[0] == '\t' || message[0] == ' ') {
5869             /* Part of the book output is here; append it */
5870             strcat(bookOutput, message);
5871             strcat(bookOutput, "  \n");
5872             return;
5873         } else if (bookOutput[0] != NULLCHAR) {
5874             /* All of book output has arrived; display it */
5875             char *p = bookOutput;
5876             while (*p != NULLCHAR) {
5877                 if (*p == '\t') *p = ' ';
5878                 p++;
5879             }
5880             DisplayInformation(bookOutput);
5881             bookRequested = FALSE;
5882             /* Fall through to parse the current output */
5883         }
5884     }
5885
5886     /*
5887      * Look for machine move.
5888      */
5889     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5890         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5891     {
5892         /* This method is only useful on engines that support ping */
5893         if (cps->lastPing != cps->lastPong) {
5894           if (gameMode == BeginningOfGame) {
5895             /* Extra move from before last new; ignore */
5896             if (appData.debugMode) {
5897                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5898             }
5899           } else {
5900             if (appData.debugMode) {
5901                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5902                         cps->which, gameMode);
5903             }
5904
5905             SendToProgram("undo\n", cps);
5906           }
5907           return;
5908         }
5909
5910         switch (gameMode) {
5911           case BeginningOfGame:
5912             /* Extra move from before last reset; ignore */
5913             if (appData.debugMode) {
5914                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5915             }
5916             return;
5917
5918           case EndOfGame:
5919           case IcsIdle:
5920           default:
5921             /* Extra move after we tried to stop.  The mode test is
5922                not a reliable way of detecting this problem, but it's
5923                the best we can do on engines that don't support ping.
5924             */
5925             if (appData.debugMode) {
5926                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5927                         cps->which, gameMode);
5928             }
5929             SendToProgram("undo\n", cps);
5930             return;
5931
5932           case MachinePlaysWhite:
5933           case IcsPlayingWhite:
5934             machineWhite = TRUE;
5935             break;
5936
5937           case MachinePlaysBlack:
5938           case IcsPlayingBlack:
5939             machineWhite = FALSE;
5940             break;
5941
5942           case TwoMachinesPlay:
5943             machineWhite = (cps->twoMachinesColor[0] == 'w');
5944             break;
5945         }
5946         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5947             if (appData.debugMode) {
5948                 fprintf(debugFP,
5949                         "Ignoring move out of turn by %s, gameMode %d"
5950                         ", forwardMost %d\n",
5951                         cps->which, gameMode, forwardMostMove);
5952             }
5953             return;
5954         }
5955
5956     if (appData.debugMode) { int f = forwardMostMove;
5957         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5958                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5959     }
5960         if(cps->alphaRank) AlphaRank(machineMove, 4);
5961         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5962                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5963             /* Machine move could not be parsed; ignore it. */
5964             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5965                     machineMove, cps->which);
5966             DisplayError(buf1, 0);
5967             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5968                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5969             if (gameMode == TwoMachinesPlay) {
5970               GameEnds(machineWhite ? BlackWins : WhiteWins,
5971                        buf1, GE_XBOARD);
5972             }
5973             return;
5974         }
5975
5976         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5977         /* So we have to redo legality test with true e.p. status here,  */
5978         /* to make sure an illegal e.p. capture does not slip through,   */
5979         /* to cause a forfeit on a justified illegal-move complaint      */
5980         /* of the opponent.                                              */
5981         if( gameMode==TwoMachinesPlay && appData.testLegality
5982             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5983                                                               ) {
5984            ChessMove moveType;
5985            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5986                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5987                              fromY, fromX, toY, toX, promoChar);
5988             if (appData.debugMode) {
5989                 int i;
5990                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5991                     castlingRights[forwardMostMove][i], castlingRank[i]);
5992                 fprintf(debugFP, "castling rights\n");
5993             }
5994             if(moveType == IllegalMove) {
5995                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5996                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5997                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5998                            buf1, GE_XBOARD);
5999                 return;
6000            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6001            /* [HGM] Kludge to handle engines that send FRC-style castling
6002               when they shouldn't (like TSCP-Gothic) */
6003            switch(moveType) {
6004              case WhiteASideCastleFR:
6005              case BlackASideCastleFR:
6006                toX+=2;
6007                currentMoveString[2]++;
6008                break;
6009              case WhiteHSideCastleFR:
6010              case BlackHSideCastleFR:
6011                toX--;
6012                currentMoveString[2]--;
6013                break;
6014              default: ; // nothing to do, but suppresses warning of pedantic compilers
6015            }
6016         }
6017         hintRequested = FALSE;
6018         lastHint[0] = NULLCHAR;
6019         bookRequested = FALSE;
6020         /* Program may be pondering now */
6021         cps->maybeThinking = TRUE;
6022         if (cps->sendTime == 2) cps->sendTime = 1;
6023         if (cps->offeredDraw) cps->offeredDraw--;
6024
6025 #if ZIPPY
6026         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6027             first.initDone) {
6028           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6029           ics_user_moved = 1;
6030           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6031                 char buf[3*MSG_SIZ];
6032
6033                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6034                         programStats.score / 100.,
6035                         programStats.depth,
6036                         programStats.time / 100.,
6037                         (unsigned int)programStats.nodes,
6038                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6039                         programStats.movelist);
6040                 SendToICS(buf);
6041 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6042           }
6043         }
6044 #endif
6045         /* currentMoveString is set as a side-effect of ParseOneMove */
6046         strcpy(machineMove, currentMoveString);
6047         strcat(machineMove, "\n");
6048         strcpy(moveList[forwardMostMove], machineMove);
6049
6050         /* [AS] Save move info and clear stats for next move */
6051         pvInfoList[ forwardMostMove ].score = programStats.score;
6052         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6053         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6054         ClearProgramStats();
6055         thinkOutput[0] = NULLCHAR;
6056         hiddenThinkOutputState = 0;
6057
6058         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6059
6060         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6061         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6062             int count = 0;
6063
6064             while( count < adjudicateLossPlies ) {
6065                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6066
6067                 if( count & 1 ) {
6068                     score = -score; /* Flip score for winning side */
6069                 }
6070
6071                 if( score > adjudicateLossThreshold ) {
6072                     break;
6073                 }
6074
6075                 count++;
6076             }
6077
6078             if( count >= adjudicateLossPlies ) {
6079                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6080
6081                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6082                     "Xboard adjudication", 
6083                     GE_XBOARD );
6084
6085                 return;
6086             }
6087         }
6088
6089         if( gameMode == TwoMachinesPlay ) {
6090           // [HGM] some adjudications useful with buggy engines
6091             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6092           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6093
6094
6095             if( appData.testLegality )
6096             {   /* [HGM] Some more adjudications for obstinate engines */
6097                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6098                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6099                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6100                 static int moveCount = 6;
6101                 ChessMove result;
6102                 char *reason = NULL;
6103
6104                 /* Count what is on board. */
6105                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6106                 {   ChessSquare p = boards[forwardMostMove][i][j];
6107                     int m=i;
6108
6109                     switch((int) p)
6110                     {   /* count B,N,R and other of each side */
6111                         case WhiteKing:
6112                         case BlackKing:
6113                              NrK++; break; // [HGM] atomic: count Kings
6114                         case WhiteKnight:
6115                              NrWN++; break;
6116                         case WhiteBishop:
6117                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6118                              bishopsColor |= 1 << ((i^j)&1);
6119                              NrWB++; break;
6120                         case BlackKnight:
6121                              NrBN++; break;
6122                         case BlackBishop:
6123                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6124                              bishopsColor |= 1 << ((i^j)&1);
6125                              NrBB++; break;
6126                         case WhiteRook:
6127                              NrWR++; break;
6128                         case BlackRook:
6129                              NrBR++; break;
6130                         case WhiteQueen:
6131                              NrWQ++; break;
6132                         case BlackQueen:
6133                              NrBQ++; break;
6134                         case EmptySquare: 
6135                              break;
6136                         case BlackPawn:
6137                              m = 7-i;
6138                         case WhitePawn:
6139                              PawnAdvance += m; NrPawns++;
6140                     }
6141                     NrPieces += (p != EmptySquare);
6142                     NrW += ((int)p < (int)BlackPawn);
6143                     if(gameInfo.variant == VariantXiangqi && 
6144                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6145                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6146                         NrW -= ((int)p < (int)BlackPawn);
6147                     }
6148                 }
6149
6150                 /* Some material-based adjudications that have to be made before stalemate test */
6151                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6152                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6153                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6154                      if(appData.checkMates) {
6155                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6156                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6157                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6158                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6159                          return;
6160                      }
6161                 }
6162
6163                 /* Bare King in Shatranj (loses) or Losers (wins) */
6164                 if( NrW == 1 || NrPieces - NrW == 1) {
6165                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6166                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6167                      if(appData.checkMates) {
6168                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6169                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6170                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6171                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6172                          return;
6173                      }
6174                   } else
6175                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6176                   {    /* bare King */
6177                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6178                         if(appData.checkMates) {
6179                             /* but only adjudicate if adjudication enabled */
6180                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6181                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6182                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6183                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6184                             return;
6185                         }
6186                   }
6187                 } else bare = 1;
6188
6189
6190             // don't wait for engine to announce game end if we can judge ourselves
6191             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6192                                        castlingRights[forwardMostMove]) ) {
6193               case MT_CHECK:
6194                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6195                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6196                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6197                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6198                             checkCnt++;
6199                         if(checkCnt >= 2) {
6200                             reason = "Xboard adjudication: 3rd check";
6201                             epStatus[forwardMostMove] = EP_CHECKMATE;
6202                             break;
6203                         }
6204                     }
6205                 }
6206               case MT_NONE:
6207               default:
6208                 break;
6209               case MT_STALEMATE:
6210               case MT_STAINMATE:
6211                 reason = "Xboard adjudication: Stalemate";
6212                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6213                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6214                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6215                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6216                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6217                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6218                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6219                                                                         EP_CHECKMATE : EP_WINS);
6220                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6221                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6222                 }
6223                 break;
6224               case MT_CHECKMATE:
6225                 reason = "Xboard adjudication: Checkmate";
6226                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6227                 break;
6228             }
6229
6230                 switch(i = epStatus[forwardMostMove]) {
6231                     case EP_STALEMATE:
6232                         result = GameIsDrawn; break;
6233                     case EP_CHECKMATE:
6234                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6235                     case EP_WINS:
6236                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6237                     default:
6238                         result = (ChessMove) 0;
6239                 }
6240                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6241                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6242                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243                     GameEnds( result, reason, GE_XBOARD );
6244                     return;
6245                 }
6246
6247                 /* Next absolutely insufficient mating material. */
6248                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6249                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6250                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6251                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6252                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6253
6254                      /* always flag draws, for judging claims */
6255                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6256
6257                      if(appData.materialDraws) {
6258                          /* but only adjudicate them if adjudication enabled */
6259                          SendToProgram("force\n", cps->other); // suppress reply
6260                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6261                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6262                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6263                          return;
6264                      }
6265                 }
6266
6267                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6268                 if(NrPieces == 4 && 
6269                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6270                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6271                    || NrWN==2 || NrBN==2     /* KNNK */
6272                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6273                   ) ) {
6274                      if(--moveCount < 0 && appData.trivialDraws)
6275                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6276                           SendToProgram("force\n", cps->other); // suppress reply
6277                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6278                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6279                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6280                           return;
6281                      }
6282                 } else moveCount = 6;
6283             }
6284           }
6285
6286                 /* Check for rep-draws */
6287                 count = 0;
6288                 for(k = forwardMostMove-2;
6289                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6290                         epStatus[k] < EP_UNKNOWN &&
6291                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6292                     k-=2)
6293                 {   int rights=0;
6294                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6295                         /* compare castling rights */
6296                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6297                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6298                                 rights++; /* King lost rights, while rook still had them */
6299                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6300                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6301                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6302                                    rights++; /* but at least one rook lost them */
6303                         }
6304                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6305                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6306                                 rights++; 
6307                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6308                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6309                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6310                                    rights++;
6311                         }
6312                         if( rights == 0 && ++count > appData.drawRepeats-2
6313                             && appData.drawRepeats > 1) {
6314                              /* adjudicate after user-specified nr of repeats */
6315                              SendToProgram("force\n", cps->other); // suppress reply
6316                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6317                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6319                                 // [HGM] xiangqi: check for forbidden perpetuals
6320                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6321                                 for(m=forwardMostMove; m>k; m-=2) {
6322                                     if(MateTest(boards[m], PosFlags(m), 
6323                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6324                                         ourPerpetual = 0; // the current mover did not always check
6325                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6326                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6327                                         hisPerpetual = 0; // the opponent did not always check
6328                                 }
6329                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6330                                                                         ourPerpetual, hisPerpetual);
6331                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6332                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6333                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6334                                     return;
6335                                 }
6336                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6337                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6338                                 // Now check for perpetual chases
6339                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6340                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6341                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6342                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6343                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6344                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6345                                         return;
6346                                     }
6347                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6348                                         break; // Abort repetition-checking loop.
6349                                 }
6350                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6351                              }
6352                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6353                              return;
6354                         }
6355                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6356                              epStatus[forwardMostMove] = EP_REP_DRAW;
6357                     }
6358                 }
6359
6360                 /* Now we test for 50-move draws. Determine ply count */
6361                 count = forwardMostMove;
6362                 /* look for last irreversble move */
6363                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6364                     count--;
6365                 /* if we hit starting position, add initial plies */
6366                 if( count == backwardMostMove )
6367                     count -= initialRulePlies;
6368                 count = forwardMostMove - count; 
6369                 if( count >= 100)
6370                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6371                          /* this is used to judge if draw claims are legal */
6372                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6373                          SendToProgram("force\n", cps->other); // suppress reply
6374                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6375                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6376                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6377                          return;
6378                 }
6379
6380                 /* if draw offer is pending, treat it as a draw claim
6381                  * when draw condition present, to allow engines a way to
6382                  * claim draws before making their move to avoid a race
6383                  * condition occurring after their move
6384                  */
6385                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6386                          char *p = NULL;
6387                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6388                              p = "Draw claim: 50-move rule";
6389                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6390                              p = "Draw claim: 3-fold repetition";
6391                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6392                              p = "Draw claim: insufficient mating material";
6393                          if( p != NULL ) {
6394                              SendToProgram("force\n", cps->other); // suppress reply
6395                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6396                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6397                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6398                              return;
6399                          }
6400                 }
6401
6402
6403                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6404                     SendToProgram("force\n", cps->other); // suppress reply
6405                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6406                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6407
6408                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6409
6410                     return;
6411                 }
6412         }
6413
6414         bookHit = NULL;
6415         if (gameMode == TwoMachinesPlay) {
6416             /* [HGM] relaying draw offers moved to after reception of move */
6417             /* and interpreting offer as claim if it brings draw condition */
6418             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6419                 SendToProgram("draw\n", cps->other);
6420             }
6421             if (cps->other->sendTime) {
6422                 SendTimeRemaining(cps->other,
6423                                   cps->other->twoMachinesColor[0] == 'w');
6424             }
6425             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6426             if (firstMove && !bookHit) {
6427                 firstMove = FALSE;
6428                 if (cps->other->useColors) {
6429                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6430                 }
6431                 SendToProgram("go\n", cps->other);
6432             }
6433             cps->other->maybeThinking = TRUE;
6434         }
6435
6436         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6437         
6438         if (!pausing && appData.ringBellAfterMoves) {
6439             RingBell();
6440         }
6441
6442         /* 
6443          * Reenable menu items that were disabled while
6444          * machine was thinking
6445          */
6446         if (gameMode != TwoMachinesPlay)
6447             SetUserThinkingEnables();
6448
6449         // [HGM] book: after book hit opponent has received move and is now in force mode
6450         // force the book reply into it, and then fake that it outputted this move by jumping
6451         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6452         if(bookHit) {
6453                 static char bookMove[MSG_SIZ]; // a bit generous?
6454
6455                 strcpy(bookMove, "move ");
6456                 strcat(bookMove, bookHit);
6457                 message = bookMove;
6458                 cps = cps->other;
6459                 programStats.nodes = programStats.depth = programStats.time = 
6460                 programStats.score = programStats.got_only_move = 0;
6461                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6462
6463                 if(cps->lastPing != cps->lastPong) {
6464                     savedMessage = message; // args for deferred call
6465                     savedState = cps;
6466                     ScheduleDelayedEvent(DeferredBookMove, 10);
6467                     return;
6468                 }
6469                 goto FakeBookMove;
6470         }
6471
6472         return;
6473     }
6474
6475     /* Set special modes for chess engines.  Later something general
6476      *  could be added here; for now there is just one kludge feature,
6477      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6478      *  when "xboard" is given as an interactive command.
6479      */
6480     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6481         cps->useSigint = FALSE;
6482         cps->useSigterm = FALSE;
6483     }
6484     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6485       ParseFeatures(message+8, cps);
6486       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6487     }
6488
6489     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6490      * want this, I was asked to put it in, and obliged.
6491      */
6492     if (!strncmp(message, "setboard ", 9)) {
6493         Board initial_position; int i;
6494
6495         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6496
6497         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6498             DisplayError(_("Bad FEN received from engine"), 0);
6499             return ;
6500         } else {
6501            Reset(TRUE, FALSE);
6502            CopyBoard(boards[0], initial_position);
6503            initialRulePlies = FENrulePlies;
6504            epStatus[0] = FENepStatus;
6505            for( i=0; i<nrCastlingRights; i++ )
6506                 castlingRights[0][i] = FENcastlingRights[i];
6507            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6508            else gameMode = MachinePlaysBlack;                 
6509            DrawPosition(FALSE, boards[currentMove]);
6510         }
6511         return;
6512     }
6513
6514     /*
6515      * Look for communication commands
6516      */
6517     if (!strncmp(message, "telluser ", 9)) {
6518         DisplayNote(message + 9);
6519         return;
6520     }
6521     if (!strncmp(message, "tellusererror ", 14)) {
6522         DisplayError(message + 14, 0);
6523         return;
6524     }
6525     if (!strncmp(message, "tellopponent ", 13)) {
6526       if (appData.icsActive) {
6527         if (loggedOn) {
6528           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6529           SendToICS(buf1);
6530         }
6531       } else {
6532         DisplayNote(message + 13);
6533       }
6534       return;
6535     }
6536     if (!strncmp(message, "tellothers ", 11)) {
6537       if (appData.icsActive) {
6538         if (loggedOn) {
6539           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6540           SendToICS(buf1);
6541         }
6542       }
6543       return;
6544     }
6545     if (!strncmp(message, "tellall ", 8)) {
6546       if (appData.icsActive) {
6547         if (loggedOn) {
6548           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6549           SendToICS(buf1);
6550         }
6551       } else {
6552         DisplayNote(message + 8);
6553       }
6554       return;
6555     }
6556     if (strncmp(message, "warning", 7) == 0) {
6557         /* Undocumented feature, use tellusererror in new code */
6558         DisplayError(message, 0);
6559         return;
6560     }
6561     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6562         strcpy(realname, cps->tidy);
6563         strcat(realname, " query");
6564         AskQuestion(realname, buf2, buf1, cps->pr);
6565         return;
6566     }
6567     /* Commands from the engine directly to ICS.  We don't allow these to be 
6568      *  sent until we are logged on. Crafty kibitzes have been known to 
6569      *  interfere with the login process.
6570      */
6571     if (loggedOn) {
6572         if (!strncmp(message, "tellics ", 8)) {
6573             SendToICS(message + 8);
6574             SendToICS("\n");
6575             return;
6576         }
6577         if (!strncmp(message, "tellicsnoalias ", 15)) {
6578             SendToICS(ics_prefix);
6579             SendToICS(message + 15);
6580             SendToICS("\n");
6581             return;
6582         }
6583         /* The following are for backward compatibility only */
6584         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6585             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6586             SendToICS(ics_prefix);
6587             SendToICS(message);
6588             SendToICS("\n");
6589             return;
6590         }
6591     }
6592     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6593         return;
6594     }
6595     /*
6596      * If the move is illegal, cancel it and redraw the board.
6597      * Also deal with other error cases.  Matching is rather loose
6598      * here to accommodate engines written before the spec.
6599      */
6600     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6601         strncmp(message, "Error", 5) == 0) {
6602         if (StrStr(message, "name") || 
6603             StrStr(message, "rating") || StrStr(message, "?") ||
6604             StrStr(message, "result") || StrStr(message, "board") ||
6605             StrStr(message, "bk") || StrStr(message, "computer") ||
6606             StrStr(message, "variant") || StrStr(message, "hint") ||
6607             StrStr(message, "random") || StrStr(message, "depth") ||
6608             StrStr(message, "accepted")) {
6609             return;
6610         }
6611         if (StrStr(message, "protover")) {
6612           /* Program is responding to input, so it's apparently done
6613              initializing, and this error message indicates it is
6614              protocol version 1.  So we don't need to wait any longer
6615              for it to initialize and send feature commands. */
6616           FeatureDone(cps, 1);
6617           cps->protocolVersion = 1;
6618           return;
6619         }
6620         cps->maybeThinking = FALSE;
6621
6622         if (StrStr(message, "draw")) {
6623             /* Program doesn't have "draw" command */
6624             cps->sendDrawOffers = 0;
6625             return;
6626         }
6627         if (cps->sendTime != 1 &&
6628             (StrStr(message, "time") || StrStr(message, "otim"))) {
6629           /* Program apparently doesn't have "time" or "otim" command */
6630           cps->sendTime = 0;
6631           return;
6632         }
6633         if (StrStr(message, "analyze")) {
6634             cps->analysisSupport = FALSE;
6635             cps->analyzing = FALSE;
6636             Reset(FALSE, TRUE);
6637             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6638             DisplayError(buf2, 0);
6639             return;
6640         }
6641         if (StrStr(message, "(no matching move)st")) {
6642           /* Special kludge for GNU Chess 4 only */
6643           cps->stKludge = TRUE;
6644           SendTimeControl(cps, movesPerSession, timeControl,
6645                           timeIncrement, appData.searchDepth,
6646                           searchTime);
6647           return;
6648         }
6649         if (StrStr(message, "(no matching move)sd")) {
6650           /* Special kludge for GNU Chess 4 only */
6651           cps->sdKludge = TRUE;
6652           SendTimeControl(cps, movesPerSession, timeControl,
6653                           timeIncrement, appData.searchDepth,
6654                           searchTime);
6655           return;
6656         }
6657         if (!StrStr(message, "llegal")) {
6658             return;
6659         }
6660         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6661             gameMode == IcsIdle) return;
6662         if (forwardMostMove <= backwardMostMove) return;
6663         if (pausing) PauseEvent();
6664       if(appData.forceIllegal) {
6665             // [HGM] illegal: machine refused move; force position after move into it
6666           SendToProgram("force\n", cps);
6667           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6668                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6669                 // when black is to move, while there might be nothing on a2 or black
6670                 // might already have the move. So send the board as if white has the move.
6671                 // But first we must change the stm of the engine, as it refused the last move
6672                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6673                 if(WhiteOnMove(forwardMostMove)) {
6674                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6675                     SendBoard(cps, forwardMostMove); // kludgeless board
6676                 } else {
6677                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6678                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6679                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6680                 }
6681           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6682             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6683                  gameMode == TwoMachinesPlay)
6684               SendToProgram("go\n", cps);
6685             return;
6686       } else
6687         if (gameMode == PlayFromGameFile) {
6688             /* Stop reading this game file */
6689             gameMode = EditGame;
6690             ModeHighlight();
6691         }
6692         currentMove = forwardMostMove-1;
6693         DisplayMove(currentMove-1); /* before DisplayMoveError */
6694         SwitchClocks(forwardMostMove-1); // [HGM] race
6695         DisplayBothClocks();
6696         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6697                 parseList[currentMove], cps->which);
6698         DisplayMoveError(buf1);
6699         DrawPosition(FALSE, boards[currentMove]);
6700
6701         /* [HGM] illegal-move claim should forfeit game when Xboard */
6702         /* only passes fully legal moves                            */
6703         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6704             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6705                                 "False illegal-move claim", GE_XBOARD );
6706         }
6707         return;
6708     }
6709     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6710         /* Program has a broken "time" command that
6711            outputs a string not ending in newline.
6712            Don't use it. */
6713         cps->sendTime = 0;
6714     }
6715     
6716     /*
6717      * If chess program startup fails, exit with an error message.
6718      * Attempts to recover here are futile.
6719      */
6720     if ((StrStr(message, "unknown host") != NULL)
6721         || (StrStr(message, "No remote directory") != NULL)
6722         || (StrStr(message, "not found") != NULL)
6723         || (StrStr(message, "No such file") != NULL)
6724         || (StrStr(message, "can't alloc") != NULL)
6725         || (StrStr(message, "Permission denied") != NULL)) {
6726
6727         cps->maybeThinking = FALSE;
6728         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6729                 cps->which, cps->program, cps->host, message);
6730         RemoveInputSource(cps->isr);
6731         DisplayFatalError(buf1, 0, 1);
6732         return;
6733     }
6734     
6735     /* 
6736      * Look for hint output
6737      */
6738     if (sscanf(message, "Hint: %s", buf1) == 1) {
6739         if (cps == &first && hintRequested) {
6740             hintRequested = FALSE;
6741             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6742                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6743                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6744                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6745                                     fromY, fromX, toY, toX, promoChar, buf1);
6746                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6747                 DisplayInformation(buf2);
6748             } else {
6749                 /* Hint move could not be parsed!? */
6750               snprintf(buf2, sizeof(buf2),
6751                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6752                         buf1, cps->which);
6753                 DisplayError(buf2, 0);
6754             }
6755         } else {
6756             strcpy(lastHint, buf1);
6757         }
6758         return;
6759     }
6760
6761     /*
6762      * Ignore other messages if game is not in progress
6763      */
6764     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6765         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6766
6767     /*
6768      * look for win, lose, draw, or draw offer
6769      */
6770     if (strncmp(message, "1-0", 3) == 0) {
6771         char *p, *q, *r = "";
6772         p = strchr(message, '{');
6773         if (p) {
6774             q = strchr(p, '}');
6775             if (q) {
6776                 *q = NULLCHAR;
6777                 r = p + 1;
6778             }
6779         }
6780         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6781         return;
6782     } else if (strncmp(message, "0-1", 3) == 0) {
6783         char *p, *q, *r = "";
6784         p = strchr(message, '{');
6785         if (p) {
6786             q = strchr(p, '}');
6787             if (q) {
6788                 *q = NULLCHAR;
6789                 r = p + 1;
6790             }
6791         }
6792         /* Kludge for Arasan 4.1 bug */
6793         if (strcmp(r, "Black resigns") == 0) {
6794             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6795             return;
6796         }
6797         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6798         return;
6799     } else if (strncmp(message, "1/2", 3) == 0) {
6800         char *p, *q, *r = "";
6801         p = strchr(message, '{');
6802         if (p) {
6803             q = strchr(p, '}');
6804             if (q) {
6805                 *q = NULLCHAR;
6806                 r = p + 1;
6807             }
6808         }
6809             
6810         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6811         return;
6812
6813     } else if (strncmp(message, "White resign", 12) == 0) {
6814         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6815         return;
6816     } else if (strncmp(message, "Black resign", 12) == 0) {
6817         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6818         return;
6819     } else if (strncmp(message, "White matches", 13) == 0 ||
6820                strncmp(message, "Black matches", 13) == 0   ) {
6821         /* [HGM] ignore GNUShogi noises */
6822         return;
6823     } else if (strncmp(message, "White", 5) == 0 &&
6824                message[5] != '(' &&
6825                StrStr(message, "Black") == NULL) {
6826         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6827         return;
6828     } else if (strncmp(message, "Black", 5) == 0 &&
6829                message[5] != '(') {
6830         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6831         return;
6832     } else if (strcmp(message, "resign") == 0 ||
6833                strcmp(message, "computer resigns") == 0) {
6834         switch (gameMode) {
6835           case MachinePlaysBlack:
6836           case IcsPlayingBlack:
6837             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6838             break;
6839           case MachinePlaysWhite:
6840           case IcsPlayingWhite:
6841             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6842             break;
6843           case TwoMachinesPlay:
6844             if (cps->twoMachinesColor[0] == 'w')
6845               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6846             else
6847               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6848             break;
6849           default:
6850             /* can't happen */
6851             break;
6852         }
6853         return;
6854     } else if (strncmp(message, "opponent mates", 14) == 0) {
6855         switch (gameMode) {
6856           case MachinePlaysBlack:
6857           case IcsPlayingBlack:
6858             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6859             break;
6860           case MachinePlaysWhite:
6861           case IcsPlayingWhite:
6862             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6863             break;
6864           case TwoMachinesPlay:
6865             if (cps->twoMachinesColor[0] == 'w')
6866               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6867             else
6868               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6869             break;
6870           default:
6871             /* can't happen */
6872             break;
6873         }
6874         return;
6875     } else if (strncmp(message, "computer mates", 14) == 0) {
6876         switch (gameMode) {
6877           case MachinePlaysBlack:
6878           case IcsPlayingBlack:
6879             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6880             break;
6881           case MachinePlaysWhite:
6882           case IcsPlayingWhite:
6883             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6884             break;
6885           case TwoMachinesPlay:
6886             if (cps->twoMachinesColor[0] == 'w')
6887               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6888             else
6889               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6890             break;
6891           default:
6892             /* can't happen */
6893             break;
6894         }
6895         return;
6896     } else if (strncmp(message, "checkmate", 9) == 0) {
6897         if (WhiteOnMove(forwardMostMove)) {
6898             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6899         } else {
6900             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6901         }
6902         return;
6903     } else if (strstr(message, "Draw") != NULL ||
6904                strstr(message, "game is a draw") != NULL) {
6905         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6906         return;
6907     } else if (strstr(message, "offer") != NULL &&
6908                strstr(message, "draw") != NULL) {
6909 #if ZIPPY
6910         if (appData.zippyPlay && first.initDone) {
6911             /* Relay offer to ICS */
6912             SendToICS(ics_prefix);
6913             SendToICS("draw\n");
6914         }
6915 #endif
6916         cps->offeredDraw = 2; /* valid until this engine moves twice */
6917         if (gameMode == TwoMachinesPlay) {
6918             if (cps->other->offeredDraw) {
6919                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6920             /* [HGM] in two-machine mode we delay relaying draw offer      */
6921             /* until after we also have move, to see if it is really claim */
6922             }
6923         } else if (gameMode == MachinePlaysWhite ||
6924                    gameMode == MachinePlaysBlack) {
6925           if (userOfferedDraw) {
6926             DisplayInformation(_("Machine accepts your draw offer"));
6927             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6928           } else {
6929             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6930           }
6931         }
6932     }
6933
6934     
6935     /*
6936      * Look for thinking output
6937      */
6938     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6939           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6940                                 ) {
6941         int plylev, mvleft, mvtot, curscore, time;
6942         char mvname[MOVE_LEN];
6943         u64 nodes; // [DM]
6944         char plyext;
6945         int ignore = FALSE;
6946         int prefixHint = FALSE;
6947         mvname[0] = NULLCHAR;
6948
6949         switch (gameMode) {
6950           case MachinePlaysBlack:
6951           case IcsPlayingBlack:
6952             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6953             break;
6954           case MachinePlaysWhite:
6955           case IcsPlayingWhite:
6956             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6957             break;
6958           case AnalyzeMode:
6959           case AnalyzeFile:
6960             break;
6961           case IcsObserving: /* [DM] icsEngineAnalyze */
6962             if (!appData.icsEngineAnalyze) ignore = TRUE;
6963             break;
6964           case TwoMachinesPlay:
6965             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6966                 ignore = TRUE;
6967             }
6968             break;
6969           default:
6970             ignore = TRUE;
6971             break;
6972         }
6973
6974         if (!ignore) {
6975             buf1[0] = NULLCHAR;
6976             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6977                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6978
6979                 if (plyext != ' ' && plyext != '\t') {
6980                     time *= 100;
6981                 }
6982
6983                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6984                 if( cps->scoreIsAbsolute && 
6985                     ( gameMode == MachinePlaysBlack ||
6986                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6987                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6988                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6989                      !WhiteOnMove(currentMove)
6990                     ) )
6991                 {
6992                     curscore = -curscore;
6993                 }
6994
6995
6996                 programStats.depth = plylev;
6997                 programStats.nodes = nodes;
6998                 programStats.time = time;
6999                 programStats.score = curscore;
7000                 programStats.got_only_move = 0;
7001
7002                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7003                         int ticklen;
7004
7005                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7006                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7007                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7008                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7009                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7010                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7011                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7012                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7013                 }
7014
7015                 /* Buffer overflow protection */
7016                 if (buf1[0] != NULLCHAR) {
7017                     if (strlen(buf1) >= sizeof(programStats.movelist)
7018                         && appData.debugMode) {
7019                         fprintf(debugFP,
7020                                 "PV is too long; using the first %u bytes.\n",
7021                                 (unsigned) sizeof(programStats.movelist) - 1);
7022                     }
7023
7024                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7025                 } else {
7026                     sprintf(programStats.movelist, " no PV\n");
7027                 }
7028
7029                 if (programStats.seen_stat) {
7030                     programStats.ok_to_send = 1;
7031                 }
7032
7033                 if (strchr(programStats.movelist, '(') != NULL) {
7034                     programStats.line_is_book = 1;
7035                     programStats.nr_moves = 0;
7036                     programStats.moves_left = 0;
7037                 } else {
7038                     programStats.line_is_book = 0;
7039                 }
7040
7041                 SendProgramStatsToFrontend( cps, &programStats );
7042
7043                 /* 
7044                     [AS] Protect the thinkOutput buffer from overflow... this
7045                     is only useful if buf1 hasn't overflowed first!
7046                 */
7047                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7048                         plylev, 
7049                         (gameMode == TwoMachinesPlay ?
7050                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7051                         ((double) curscore) / 100.0,
7052                         prefixHint ? lastHint : "",
7053                         prefixHint ? " " : "" );
7054
7055                 if( buf1[0] != NULLCHAR ) {
7056                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7057
7058                     if( strlen(buf1) > max_len ) {
7059                         if( appData.debugMode) {
7060                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7061                         }
7062                         buf1[max_len+1] = '\0';
7063                     }
7064
7065                     strcat( thinkOutput, buf1 );
7066                 }
7067
7068                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7069                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7070                     DisplayMove(currentMove - 1);
7071                 }
7072                 return;
7073
7074             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7075                 /* crafty (9.25+) says "(only move) <move>"
7076                  * if there is only 1 legal move
7077                  */
7078                 sscanf(p, "(only move) %s", buf1);
7079                 sprintf(thinkOutput, "%s (only move)", buf1);
7080                 sprintf(programStats.movelist, "%s (only move)", buf1);
7081                 programStats.depth = 1;
7082                 programStats.nr_moves = 1;
7083                 programStats.moves_left = 1;
7084                 programStats.nodes = 1;
7085                 programStats.time = 1;
7086                 programStats.got_only_move = 1;
7087
7088                 /* Not really, but we also use this member to
7089                    mean "line isn't going to change" (Crafty
7090                    isn't searching, so stats won't change) */
7091                 programStats.line_is_book = 1;
7092
7093                 SendProgramStatsToFrontend( cps, &programStats );
7094                 
7095                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7096                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7097                     DisplayMove(currentMove - 1);
7098                 }
7099                 return;
7100             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7101                               &time, &nodes, &plylev, &mvleft,
7102                               &mvtot, mvname) >= 5) {
7103                 /* The stat01: line is from Crafty (9.29+) in response
7104                    to the "." command */
7105                 programStats.seen_stat = 1;
7106                 cps->maybeThinking = TRUE;
7107
7108                 if (programStats.got_only_move || !appData.periodicUpdates)
7109                   return;
7110
7111                 programStats.depth = plylev;
7112                 programStats.time = time;
7113                 programStats.nodes = nodes;
7114                 programStats.moves_left = mvleft;
7115                 programStats.nr_moves = mvtot;
7116                 strcpy(programStats.move_name, mvname);
7117                 programStats.ok_to_send = 1;
7118                 programStats.movelist[0] = '\0';
7119
7120                 SendProgramStatsToFrontend( cps, &programStats );
7121
7122                 return;
7123
7124             } else if (strncmp(message,"++",2) == 0) {
7125                 /* Crafty 9.29+ outputs this */
7126                 programStats.got_fail = 2;
7127                 return;
7128
7129             } else if (strncmp(message,"--",2) == 0) {
7130                 /* Crafty 9.29+ outputs this */
7131                 programStats.got_fail = 1;
7132                 return;
7133
7134             } else if (thinkOutput[0] != NULLCHAR &&
7135                        strncmp(message, "    ", 4) == 0) {
7136                 unsigned message_len;
7137
7138                 p = message;
7139                 while (*p && *p == ' ') p++;
7140
7141                 message_len = strlen( p );
7142
7143                 /* [AS] Avoid buffer overflow */
7144                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7145                     strcat(thinkOutput, " ");
7146                     strcat(thinkOutput, p);
7147                 }
7148
7149                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7150                     strcat(programStats.movelist, " ");
7151                     strcat(programStats.movelist, p);
7152                 }
7153
7154                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7155                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7156                     DisplayMove(currentMove - 1);
7157                 }
7158                 return;
7159             }
7160         }
7161         else {
7162             buf1[0] = NULLCHAR;
7163
7164             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7165                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7166             {
7167                 ChessProgramStats cpstats;
7168
7169                 if (plyext != ' ' && plyext != '\t') {
7170                     time *= 100;
7171                 }
7172
7173                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7174                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7175                     curscore = -curscore;
7176                 }
7177
7178                 cpstats.depth = plylev;
7179                 cpstats.nodes = nodes;
7180                 cpstats.time = time;
7181                 cpstats.score = curscore;
7182                 cpstats.got_only_move = 0;
7183                 cpstats.movelist[0] = '\0';
7184
7185                 if (buf1[0] != NULLCHAR) {
7186                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7187                 }
7188
7189                 cpstats.ok_to_send = 0;
7190                 cpstats.line_is_book = 0;
7191                 cpstats.nr_moves = 0;
7192                 cpstats.moves_left = 0;
7193
7194                 SendProgramStatsToFrontend( cps, &cpstats );
7195             }
7196         }
7197     }
7198 }
7199
7200
7201 /* Parse a game score from the character string "game", and
7202    record it as the history of the current game.  The game
7203    score is NOT assumed to start from the standard position. 
7204    The display is not updated in any way.
7205    */
7206 void
7207 ParseGameHistory(game)
7208      char *game;
7209 {
7210     ChessMove moveType;
7211     int fromX, fromY, toX, toY, boardIndex;
7212     char promoChar;
7213     char *p, *q;
7214     char buf[MSG_SIZ];
7215
7216     if (appData.debugMode)
7217       fprintf(debugFP, "Parsing game history: %s\n", game);
7218
7219     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7220     gameInfo.site = StrSave(appData.icsHost);
7221     gameInfo.date = PGNDate();
7222     gameInfo.round = StrSave("-");
7223
7224     /* Parse out names of players */
7225     while (*game == ' ') game++;
7226     p = buf;
7227     while (*game != ' ') *p++ = *game++;
7228     *p = NULLCHAR;
7229     gameInfo.white = StrSave(buf);
7230     while (*game == ' ') game++;
7231     p = buf;
7232     while (*game != ' ' && *game != '\n') *p++ = *game++;
7233     *p = NULLCHAR;
7234     gameInfo.black = StrSave(buf);
7235
7236     /* Parse moves */
7237     boardIndex = blackPlaysFirst ? 1 : 0;
7238     yynewstr(game);
7239     for (;;) {
7240         yyboardindex = boardIndex;
7241         moveType = (ChessMove) yylex();
7242         switch (moveType) {
7243           case IllegalMove:             /* maybe suicide chess, etc. */
7244   if (appData.debugMode) {
7245     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247     setbuf(debugFP, NULL);
7248   }
7249           case WhitePromotionChancellor:
7250           case BlackPromotionChancellor:
7251           case WhitePromotionArchbishop:
7252           case BlackPromotionArchbishop:
7253           case WhitePromotionQueen:
7254           case BlackPromotionQueen:
7255           case WhitePromotionRook:
7256           case BlackPromotionRook:
7257           case WhitePromotionBishop:
7258           case BlackPromotionBishop:
7259           case WhitePromotionKnight:
7260           case BlackPromotionKnight:
7261           case WhitePromotionKing:
7262           case BlackPromotionKing:
7263           case NormalMove:
7264           case WhiteCapturesEnPassant:
7265           case BlackCapturesEnPassant:
7266           case WhiteKingSideCastle:
7267           case WhiteQueenSideCastle:
7268           case BlackKingSideCastle:
7269           case BlackQueenSideCastle:
7270           case WhiteKingSideCastleWild:
7271           case WhiteQueenSideCastleWild:
7272           case BlackKingSideCastleWild:
7273           case BlackQueenSideCastleWild:
7274           /* PUSH Fabien */
7275           case WhiteHSideCastleFR:
7276           case WhiteASideCastleFR:
7277           case BlackHSideCastleFR:
7278           case BlackASideCastleFR:
7279           /* POP Fabien */
7280             fromX = currentMoveString[0] - AAA;
7281             fromY = currentMoveString[1] - ONE;
7282             toX = currentMoveString[2] - AAA;
7283             toY = currentMoveString[3] - ONE;
7284             promoChar = currentMoveString[4];
7285             break;
7286           case WhiteDrop:
7287           case BlackDrop:
7288             fromX = moveType == WhiteDrop ?
7289               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7290             (int) CharToPiece(ToLower(currentMoveString[0]));
7291             fromY = DROP_RANK;
7292             toX = currentMoveString[2] - AAA;
7293             toY = currentMoveString[3] - ONE;
7294             promoChar = NULLCHAR;
7295             break;
7296           case AmbiguousMove:
7297             /* bug? */
7298             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7299   if (appData.debugMode) {
7300     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7301     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7302     setbuf(debugFP, NULL);
7303   }
7304             DisplayError(buf, 0);
7305             return;
7306           case ImpossibleMove:
7307             /* bug? */
7308             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7309   if (appData.debugMode) {
7310     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7311     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7312     setbuf(debugFP, NULL);
7313   }
7314             DisplayError(buf, 0);
7315             return;
7316           case (ChessMove) 0:   /* end of file */
7317             if (boardIndex < backwardMostMove) {
7318                 /* Oops, gap.  How did that happen? */
7319                 DisplayError(_("Gap in move list"), 0);
7320                 return;
7321             }
7322             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7323             if (boardIndex > forwardMostMove) {
7324                 forwardMostMove = boardIndex;
7325             }
7326             return;
7327           case ElapsedTime:
7328             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7329                 strcat(parseList[boardIndex-1], " ");
7330                 strcat(parseList[boardIndex-1], yy_text);
7331             }
7332             continue;
7333           case Comment:
7334           case PGNTag:
7335           case NAG:
7336           default:
7337             /* ignore */
7338             continue;
7339           case WhiteWins:
7340           case BlackWins:
7341           case GameIsDrawn:
7342           case GameUnfinished:
7343             if (gameMode == IcsExamining) {
7344                 if (boardIndex < backwardMostMove) {
7345                     /* Oops, gap.  How did that happen? */
7346                     return;
7347                 }
7348                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7349                 return;
7350             }
7351             gameInfo.result = moveType;
7352             p = strchr(yy_text, '{');
7353             if (p == NULL) p = strchr(yy_text, '(');
7354             if (p == NULL) {
7355                 p = yy_text;
7356                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7357             } else {
7358                 q = strchr(p, *p == '{' ? '}' : ')');
7359                 if (q != NULL) *q = NULLCHAR;
7360                 p++;
7361             }
7362             gameInfo.resultDetails = StrSave(p);
7363             continue;
7364         }
7365         if (boardIndex >= forwardMostMove &&
7366             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7367             backwardMostMove = blackPlaysFirst ? 1 : 0;
7368             return;
7369         }
7370         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7371                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7372                                  parseList[boardIndex]);
7373         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7374         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7375         /* currentMoveString is set as a side-effect of yylex */
7376         strcpy(moveList[boardIndex], currentMoveString);
7377         strcat(moveList[boardIndex], "\n");
7378         boardIndex++;
7379         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7380                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7381         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7382                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7383           case MT_NONE:
7384           case MT_STALEMATE:
7385           default:
7386             break;
7387           case MT_CHECK:
7388             if(gameInfo.variant != VariantShogi)
7389                 strcat(parseList[boardIndex - 1], "+");
7390             break;
7391           case MT_CHECKMATE:
7392           case MT_STAINMATE:
7393             strcat(parseList[boardIndex - 1], "#");
7394             break;
7395         }
7396     }
7397 }
7398
7399
7400 /* Apply a move to the given board  */
7401 void
7402 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7403      int fromX, fromY, toX, toY;
7404      int promoChar;
7405      Board board;
7406      char *castling;
7407      char *ep;
7408 {
7409   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7410   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7411
7412     /* [HGM] compute & store e.p. status and castling rights for new position */
7413     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7414     { int i;
7415
7416       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7417       oldEP = *ep;
7418       *ep = EP_NONE;
7419
7420       if( board[toY][toX] != EmptySquare ) 
7421            *ep = EP_CAPTURE;  
7422
7423       if( board[fromY][fromX] == WhitePawn ) {
7424            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7425                *ep = EP_PAWN_MOVE;
7426            if( toY-fromY==2) {
7427                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7428                         gameInfo.variant != VariantBerolina || toX < fromX)
7429                       *ep = toX | berolina;
7430                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7431                         gameInfo.variant != VariantBerolina || toX > fromX) 
7432                       *ep = toX;
7433            }
7434       } else 
7435       if( board[fromY][fromX] == BlackPawn ) {
7436            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7437                *ep = EP_PAWN_MOVE; 
7438            if( toY-fromY== -2) {
7439                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7440                         gameInfo.variant != VariantBerolina || toX < fromX)
7441                       *ep = toX | berolina;
7442                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7443                         gameInfo.variant != VariantBerolina || toX > fromX) 
7444                       *ep = toX;
7445            }
7446        }
7447
7448        for(i=0; i<nrCastlingRights; i++) {
7449            if(castling[i] == fromX && castlingRank[i] == fromY ||
7450               castling[i] == toX   && castlingRank[i] == toY   
7451              ) castling[i] = -1; // revoke for moved or captured piece
7452        }
7453
7454     }
7455
7456   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7457   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7458        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7459          
7460   if (fromX == toX && fromY == toY) return;
7461
7462   if (fromY == DROP_RANK) {
7463         /* must be first */
7464         piece = board[toY][toX] = (ChessSquare) fromX;
7465   } else {
7466      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7467      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7468      if(gameInfo.variant == VariantKnightmate)
7469          king += (int) WhiteUnicorn - (int) WhiteKing;
7470
7471     /* Code added by Tord: */
7472     /* FRC castling assumed when king captures friendly rook. */
7473     if (board[fromY][fromX] == WhiteKing &&
7474              board[toY][toX] == WhiteRook) {
7475       board[fromY][fromX] = EmptySquare;
7476       board[toY][toX] = EmptySquare;
7477       if(toX > fromX) {
7478         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7479       } else {
7480         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7481       }
7482     } else if (board[fromY][fromX] == BlackKing &&
7483                board[toY][toX] == BlackRook) {
7484       board[fromY][fromX] = EmptySquare;
7485       board[toY][toX] = EmptySquare;
7486       if(toX > fromX) {
7487         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7488       } else {
7489         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7490       }
7491     /* End of code added by Tord */
7492
7493     } else if (board[fromY][fromX] == king
7494         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7495         && toY == fromY && toX > fromX+1) {
7496         board[fromY][fromX] = EmptySquare;
7497         board[toY][toX] = king;
7498         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7499         board[fromY][BOARD_RGHT-1] = EmptySquare;
7500     } else if (board[fromY][fromX] == king
7501         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7502                && toY == fromY && toX < fromX-1) {
7503         board[fromY][fromX] = EmptySquare;
7504         board[toY][toX] = king;
7505         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7506         board[fromY][BOARD_LEFT] = EmptySquare;
7507     } else if (board[fromY][fromX] == WhitePawn
7508                && toY >= BOARD_HEIGHT-promoRank
7509                && gameInfo.variant != VariantXiangqi
7510                ) {
7511         /* white pawn promotion */
7512         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7513         if (board[toY][toX] == EmptySquare) {
7514             board[toY][toX] = WhiteQueen;
7515         }
7516         if(gameInfo.variant==VariantBughouse ||
7517            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7518             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7519         board[fromY][fromX] = EmptySquare;
7520     } else if ((fromY == BOARD_HEIGHT-4)
7521                && (toX != fromX)
7522                && gameInfo.variant != VariantXiangqi
7523                && gameInfo.variant != VariantBerolina
7524                && (board[fromY][fromX] == WhitePawn)
7525                && (board[toY][toX] == EmptySquare)) {
7526         board[fromY][fromX] = EmptySquare;
7527         board[toY][toX] = WhitePawn;
7528         captured = board[toY - 1][toX];
7529         board[toY - 1][toX] = EmptySquare;
7530     } else if ((fromY == BOARD_HEIGHT-4)
7531                && (toX == fromX)
7532                && gameInfo.variant == VariantBerolina
7533                && (board[fromY][fromX] == WhitePawn)
7534                && (board[toY][toX] == EmptySquare)) {
7535         board[fromY][fromX] = EmptySquare;
7536         board[toY][toX] = WhitePawn;
7537         if(oldEP & EP_BEROLIN_A) {
7538                 captured = board[fromY][fromX-1];
7539                 board[fromY][fromX-1] = EmptySquare;
7540         }else{  captured = board[fromY][fromX+1];
7541                 board[fromY][fromX+1] = EmptySquare;
7542         }
7543     } else if (board[fromY][fromX] == king
7544         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7545                && toY == fromY && toX > fromX+1) {
7546         board[fromY][fromX] = EmptySquare;
7547         board[toY][toX] = king;
7548         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7549         board[fromY][BOARD_RGHT-1] = EmptySquare;
7550     } else if (board[fromY][fromX] == king
7551         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7552                && toY == fromY && toX < fromX-1) {
7553         board[fromY][fromX] = EmptySquare;
7554         board[toY][toX] = king;
7555         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7556         board[fromY][BOARD_LEFT] = EmptySquare;
7557     } else if (fromY == 7 && fromX == 3
7558                && board[fromY][fromX] == BlackKing
7559                && toY == 7 && toX == 5) {
7560         board[fromY][fromX] = EmptySquare;
7561         board[toY][toX] = BlackKing;
7562         board[fromY][7] = EmptySquare;
7563         board[toY][4] = BlackRook;
7564     } else if (fromY == 7 && fromX == 3
7565                && board[fromY][fromX] == BlackKing
7566                && toY == 7 && toX == 1) {
7567         board[fromY][fromX] = EmptySquare;
7568         board[toY][toX] = BlackKing;
7569         board[fromY][0] = EmptySquare;
7570         board[toY][2] = BlackRook;
7571     } else if (board[fromY][fromX] == BlackPawn
7572                && toY < promoRank
7573                && gameInfo.variant != VariantXiangqi
7574                ) {
7575         /* black pawn promotion */
7576         board[toY][toX] = CharToPiece(ToLower(promoChar));
7577         if (board[toY][toX] == EmptySquare) {
7578             board[toY][toX] = BlackQueen;
7579         }
7580         if(gameInfo.variant==VariantBughouse ||
7581            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7582             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7583         board[fromY][fromX] = EmptySquare;
7584     } else if ((fromY == 3)
7585                && (toX != fromX)
7586                && gameInfo.variant != VariantXiangqi
7587                && gameInfo.variant != VariantBerolina
7588                && (board[fromY][fromX] == BlackPawn)
7589                && (board[toY][toX] == EmptySquare)) {
7590         board[fromY][fromX] = EmptySquare;
7591         board[toY][toX] = BlackPawn;
7592         captured = board[toY + 1][toX];
7593         board[toY + 1][toX] = EmptySquare;
7594     } else if ((fromY == 3)
7595                && (toX == fromX)
7596                && gameInfo.variant == VariantBerolina
7597                && (board[fromY][fromX] == BlackPawn)
7598                && (board[toY][toX] == EmptySquare)) {
7599         board[fromY][fromX] = EmptySquare;
7600         board[toY][toX] = BlackPawn;
7601         if(oldEP & EP_BEROLIN_A) {
7602                 captured = board[fromY][fromX-1];
7603                 board[fromY][fromX-1] = EmptySquare;
7604         }else{  captured = board[fromY][fromX+1];
7605                 board[fromY][fromX+1] = EmptySquare;
7606         }
7607     } else {
7608         board[toY][toX] = board[fromY][fromX];
7609         board[fromY][fromX] = EmptySquare;
7610     }
7611
7612     /* [HGM] now we promote for Shogi, if needed */
7613     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7614         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7615   }
7616
7617     if (gameInfo.holdingsWidth != 0) {
7618
7619       /* !!A lot more code needs to be written to support holdings  */
7620       /* [HGM] OK, so I have written it. Holdings are stored in the */
7621       /* penultimate board files, so they are automaticlly stored   */
7622       /* in the game history.                                       */
7623       if (fromY == DROP_RANK) {
7624         /* Delete from holdings, by decreasing count */
7625         /* and erasing image if necessary            */
7626         p = (int) fromX;
7627         if(p < (int) BlackPawn) { /* white drop */
7628              p -= (int)WhitePawn;
7629                  p = PieceToNumber((ChessSquare)p);
7630              if(p >= gameInfo.holdingsSize) p = 0;
7631              if(--board[p][BOARD_WIDTH-2] <= 0)
7632                   board[p][BOARD_WIDTH-1] = EmptySquare;
7633              if((int)board[p][BOARD_WIDTH-2] < 0)
7634                         board[p][BOARD_WIDTH-2] = 0;
7635         } else {                  /* black drop */
7636              p -= (int)BlackPawn;
7637                  p = PieceToNumber((ChessSquare)p);
7638              if(p >= gameInfo.holdingsSize) p = 0;
7639              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7640                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7641              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7642                         board[BOARD_HEIGHT-1-p][1] = 0;
7643         }
7644       }
7645       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7646           && gameInfo.variant != VariantBughouse        ) {
7647         /* [HGM] holdings: Add to holdings, if holdings exist */
7648         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7649                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7650                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7651         }
7652         p = (int) captured;
7653         if (p >= (int) BlackPawn) {
7654           p -= (int)BlackPawn;
7655           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7656                   /* in Shogi restore piece to its original  first */
7657                   captured = (ChessSquare) (DEMOTED captured);
7658                   p = DEMOTED p;
7659           }
7660           p = PieceToNumber((ChessSquare)p);
7661           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7662           board[p][BOARD_WIDTH-2]++;
7663           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7664         } else {
7665           p -= (int)WhitePawn;
7666           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7667                   captured = (ChessSquare) (DEMOTED captured);
7668                   p = DEMOTED p;
7669           }
7670           p = PieceToNumber((ChessSquare)p);
7671           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7672           board[BOARD_HEIGHT-1-p][1]++;
7673           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7674         }
7675       }
7676     } else if (gameInfo.variant == VariantAtomic) {
7677       if (captured != EmptySquare) {
7678         int y, x;
7679         for (y = toY-1; y <= toY+1; y++) {
7680           for (x = toX-1; x <= toX+1; x++) {
7681             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7682                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7683               board[y][x] = EmptySquare;
7684             }
7685           }
7686         }
7687         board[toY][toX] = EmptySquare;
7688       }
7689     }
7690     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7691         /* [HGM] Shogi promotions */
7692         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7693     }
7694
7695     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7696                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7697         // [HGM] superchess: take promotion piece out of holdings
7698         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7699         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7700             if(!--board[k][BOARD_WIDTH-2])
7701                 board[k][BOARD_WIDTH-1] = EmptySquare;
7702         } else {
7703             if(!--board[BOARD_HEIGHT-1-k][1])
7704                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7705         }
7706     }
7707
7708 }
7709
7710 /* Updates forwardMostMove */
7711 void
7712 MakeMove(fromX, fromY, toX, toY, promoChar)
7713      int fromX, fromY, toX, toY;
7714      int promoChar;
7715 {
7716 //    forwardMostMove++; // [HGM] bare: moved downstream
7717
7718     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7719         int timeLeft; static int lastLoadFlag=0; int king, piece;
7720         piece = boards[forwardMostMove][fromY][fromX];
7721         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7722         if(gameInfo.variant == VariantKnightmate)
7723             king += (int) WhiteUnicorn - (int) WhiteKing;
7724         if(forwardMostMove == 0) {
7725             if(blackPlaysFirst) 
7726                 fprintf(serverMoves, "%s;", second.tidy);
7727             fprintf(serverMoves, "%s;", first.tidy);
7728             if(!blackPlaysFirst) 
7729                 fprintf(serverMoves, "%s;", second.tidy);
7730         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7731         lastLoadFlag = loadFlag;
7732         // print base move
7733         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7734         // print castling suffix
7735         if( toY == fromY && piece == king ) {
7736             if(toX-fromX > 1)
7737                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7738             if(fromX-toX >1)
7739                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7740         }
7741         // e.p. suffix
7742         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7743              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7744              boards[forwardMostMove][toY][toX] == EmptySquare
7745              && fromX != toX )
7746                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7747         // promotion suffix
7748         if(promoChar != NULLCHAR)
7749                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7750         if(!loadFlag) {
7751             fprintf(serverMoves, "/%d/%d",
7752                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7753             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7754             else                      timeLeft = blackTimeRemaining/1000;
7755             fprintf(serverMoves, "/%d", timeLeft);
7756         }
7757         fflush(serverMoves);
7758     }
7759
7760     if (forwardMostMove+1 >= MAX_MOVES) {
7761       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7762                         0, 1);
7763       return;
7764     }
7765     if (commentList[forwardMostMove+1] != NULL) {
7766         free(commentList[forwardMostMove+1]);
7767         commentList[forwardMostMove+1] = NULL;
7768     }
7769     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7770     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7771     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7772                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7773     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7774     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7775     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7776     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7777     gameInfo.result = GameUnfinished;
7778     if (gameInfo.resultDetails != NULL) {
7779         free(gameInfo.resultDetails);
7780         gameInfo.resultDetails = NULL;
7781     }
7782     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7783                               moveList[forwardMostMove - 1]);
7784     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7785                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7786                              fromY, fromX, toY, toX, promoChar,
7787                              parseList[forwardMostMove - 1]);
7788     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7789                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7790                             castlingRights[forwardMostMove]) ) {
7791       case MT_NONE:
7792       case MT_STALEMATE:
7793       default:
7794         break;
7795       case MT_CHECK:
7796         if(gameInfo.variant != VariantShogi)
7797             strcat(parseList[forwardMostMove - 1], "+");
7798         break;
7799       case MT_CHECKMATE:
7800       case MT_STAINMATE:
7801         strcat(parseList[forwardMostMove - 1], "#");
7802         break;
7803     }
7804     if (appData.debugMode) {
7805         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7806     }
7807
7808 }
7809
7810 /* Updates currentMove if not pausing */
7811 void
7812 ShowMove(fromX, fromY, toX, toY)
7813 {
7814     int instant = (gameMode == PlayFromGameFile) ?
7815         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7816     if(appData.noGUI) return;
7817     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7818         if (!instant) {
7819             if (forwardMostMove == currentMove + 1) {
7820                 AnimateMove(boards[forwardMostMove - 1],
7821                             fromX, fromY, toX, toY);
7822             }
7823             if (appData.highlightLastMove) {
7824                 SetHighlights(fromX, fromY, toX, toY);
7825             }
7826         }
7827         currentMove = forwardMostMove;
7828     }
7829
7830     if (instant) return;
7831
7832     DisplayMove(currentMove - 1);
7833     DrawPosition(FALSE, boards[currentMove]);
7834     DisplayBothClocks();
7835     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7836 }
7837
7838 void SendEgtPath(ChessProgramState *cps)
7839 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7840         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7841
7842         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7843
7844         while(*p) {
7845             char c, *q = name+1, *r, *s;
7846
7847             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7848             while(*p && *p != ',') *q++ = *p++;
7849             *q++ = ':'; *q = 0;
7850             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7851                 strcmp(name, ",nalimov:") == 0 ) {
7852                 // take nalimov path from the menu-changeable option first, if it is defined
7853                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7854                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7855             } else
7856             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7857                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7858                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7859                 s = r = StrStr(s, ":") + 1; // beginning of path info
7860                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7861                 c = *r; *r = 0;             // temporarily null-terminate path info
7862                     *--q = 0;               // strip of trailig ':' from name
7863                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7864                 *r = c;
7865                 SendToProgram(buf,cps);     // send egtbpath command for this format
7866             }
7867             if(*p == ',') p++; // read away comma to position for next format name
7868         }
7869 }
7870
7871 void
7872 InitChessProgram(cps, setup)
7873      ChessProgramState *cps;
7874      int setup; /* [HGM] needed to setup FRC opening position */
7875 {
7876     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7877     if (appData.noChessProgram) return;
7878     hintRequested = FALSE;
7879     bookRequested = FALSE;
7880
7881     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7882     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7883     if(cps->memSize) { /* [HGM] memory */
7884         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7885         SendToProgram(buf, cps);
7886     }
7887     SendEgtPath(cps); /* [HGM] EGT */
7888     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7889         sprintf(buf, "cores %d\n", appData.smpCores);
7890         SendToProgram(buf, cps);
7891     }
7892
7893     SendToProgram(cps->initString, cps);
7894     if (gameInfo.variant != VariantNormal &&
7895         gameInfo.variant != VariantLoadable
7896         /* [HGM] also send variant if board size non-standard */
7897         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7898                                             ) {
7899       char *v = VariantName(gameInfo.variant);
7900       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7901         /* [HGM] in protocol 1 we have to assume all variants valid */
7902         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7903         DisplayFatalError(buf, 0, 1);
7904         return;
7905       }
7906
7907       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7908       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7909       if( gameInfo.variant == VariantXiangqi )
7910            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7911       if( gameInfo.variant == VariantShogi )
7912            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7913       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7914            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7915       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7916                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7917            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7918       if( gameInfo.variant == VariantCourier )
7919            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7920       if( gameInfo.variant == VariantSuper )
7921            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7922       if( gameInfo.variant == VariantGreat )
7923            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7924
7925       if(overruled) {
7926            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7927                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7928            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7929            if(StrStr(cps->variants, b) == NULL) { 
7930                // specific sized variant not known, check if general sizing allowed
7931                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7932                    if(StrStr(cps->variants, "boardsize") == NULL) {
7933                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7934                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7935                        DisplayFatalError(buf, 0, 1);
7936                        return;
7937                    }
7938                    /* [HGM] here we really should compare with the maximum supported board size */
7939                }
7940            }
7941       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7942       sprintf(buf, "variant %s\n", b);
7943       SendToProgram(buf, cps);
7944     }
7945     currentlyInitializedVariant = gameInfo.variant;
7946
7947     /* [HGM] send opening position in FRC to first engine */
7948     if(setup) {
7949           SendToProgram("force\n", cps);
7950           SendBoard(cps, 0);
7951           /* engine is now in force mode! Set flag to wake it up after first move. */
7952           setboardSpoiledMachineBlack = 1;
7953     }
7954
7955     if (cps->sendICS) {
7956       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7957       SendToProgram(buf, cps);
7958     }
7959     cps->maybeThinking = FALSE;
7960     cps->offeredDraw = 0;
7961     if (!appData.icsActive) {
7962         SendTimeControl(cps, movesPerSession, timeControl,
7963                         timeIncrement, appData.searchDepth,
7964                         searchTime);
7965     }
7966     if (appData.showThinking 
7967         // [HGM] thinking: four options require thinking output to be sent
7968         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7969                                 ) {
7970         SendToProgram("post\n", cps);
7971     }
7972     SendToProgram("hard\n", cps);
7973     if (!appData.ponderNextMove) {
7974         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7975            it without being sure what state we are in first.  "hard"
7976            is not a toggle, so that one is OK.
7977          */
7978         SendToProgram("easy\n", cps);
7979     }
7980     if (cps->usePing) {
7981       sprintf(buf, "ping %d\n", ++cps->lastPing);
7982       SendToProgram(buf, cps);
7983     }
7984     cps->initDone = TRUE;
7985 }   
7986
7987
7988 void
7989 StartChessProgram(cps)
7990      ChessProgramState *cps;
7991 {
7992     char buf[MSG_SIZ];
7993     int err;
7994
7995     if (appData.noChessProgram) return;
7996     cps->initDone = FALSE;
7997
7998     if (strcmp(cps->host, "localhost") == 0) {
7999         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8000     } else if (*appData.remoteShell == NULLCHAR) {
8001         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8002     } else {
8003         if (*appData.remoteUser == NULLCHAR) {
8004           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8005                     cps->program);
8006         } else {
8007           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8008                     cps->host, appData.remoteUser, cps->program);
8009         }
8010         err = StartChildProcess(buf, "", &cps->pr);
8011     }
8012     
8013     if (err != 0) {
8014         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8015         DisplayFatalError(buf, err, 1);
8016         cps->pr = NoProc;
8017         cps->isr = NULL;
8018         return;
8019     }
8020     
8021     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8022     if (cps->protocolVersion > 1) {
8023       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8024       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8025       cps->comboCnt = 0;  //                and values of combo boxes
8026       SendToProgram(buf, cps);
8027     } else {
8028       SendToProgram("xboard\n", cps);
8029     }
8030 }
8031
8032
8033 void
8034 TwoMachinesEventIfReady P((void))
8035 {
8036   if (first.lastPing != first.lastPong) {
8037     DisplayMessage("", _("Waiting for first chess program"));
8038     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8039     return;
8040   }
8041   if (second.lastPing != second.lastPong) {
8042     DisplayMessage("", _("Waiting for second chess program"));
8043     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8044     return;
8045   }
8046   ThawUI();
8047   TwoMachinesEvent();
8048 }
8049
8050 void
8051 NextMatchGame P((void))
8052 {
8053     int index; /* [HGM] autoinc: step load index during match */
8054     Reset(FALSE, TRUE);
8055     if (*appData.loadGameFile != NULLCHAR) {
8056         index = appData.loadGameIndex;
8057         if(index < 0) { // [HGM] autoinc
8058             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8059             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8060         } 
8061         LoadGameFromFile(appData.loadGameFile,
8062                          index,
8063                          appData.loadGameFile, FALSE);
8064     } else if (*appData.loadPositionFile != NULLCHAR) {
8065         index = appData.loadPositionIndex;
8066         if(index < 0) { // [HGM] autoinc
8067             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8068             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8069         } 
8070         LoadPositionFromFile(appData.loadPositionFile,
8071                              index,
8072                              appData.loadPositionFile);
8073     }
8074     TwoMachinesEventIfReady();
8075 }
8076
8077 void UserAdjudicationEvent( int result )
8078 {
8079     ChessMove gameResult = GameIsDrawn;
8080
8081     if( result > 0 ) {
8082         gameResult = WhiteWins;
8083     }
8084     else if( result < 0 ) {
8085         gameResult = BlackWins;
8086     }
8087
8088     if( gameMode == TwoMachinesPlay ) {
8089         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8090     }
8091 }
8092
8093
8094 // [HGM] save: calculate checksum of game to make games easily identifiable
8095 int StringCheckSum(char *s)
8096 {
8097         int i = 0;
8098         if(s==NULL) return 0;
8099         while(*s) i = i*259 + *s++;
8100         return i;
8101 }
8102
8103 int GameCheckSum()
8104 {
8105         int i, sum=0;
8106         for(i=backwardMostMove; i<forwardMostMove; i++) {
8107                 sum += pvInfoList[i].depth;
8108                 sum += StringCheckSum(parseList[i]);
8109                 sum += StringCheckSum(commentList[i]);
8110                 sum *= 261;
8111         }
8112         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8113         return sum + StringCheckSum(commentList[i]);
8114 } // end of save patch
8115
8116 void
8117 GameEnds(result, resultDetails, whosays)
8118      ChessMove result;
8119      char *resultDetails;
8120      int whosays;
8121 {
8122     GameMode nextGameMode;
8123     int isIcsGame;
8124     char buf[MSG_SIZ];
8125
8126     if(endingGame) return; /* [HGM] crash: forbid recursion */
8127     endingGame = 1;
8128
8129     if (appData.debugMode) {
8130       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8131               result, resultDetails ? resultDetails : "(null)", whosays);
8132     }
8133
8134     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8135
8136     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8137         /* If we are playing on ICS, the server decides when the
8138            game is over, but the engine can offer to draw, claim 
8139            a draw, or resign. 
8140          */
8141 #if ZIPPY
8142         if (appData.zippyPlay && first.initDone) {
8143             if (result == GameIsDrawn) {
8144                 /* In case draw still needs to be claimed */
8145                 SendToICS(ics_prefix);
8146                 SendToICS("draw\n");
8147             } else if (StrCaseStr(resultDetails, "resign")) {
8148                 SendToICS(ics_prefix);
8149                 SendToICS("resign\n");
8150             }
8151         }
8152 #endif
8153         endingGame = 0; /* [HGM] crash */
8154         return;
8155     }
8156
8157     /* If we're loading the game from a file, stop */
8158     if (whosays == GE_FILE) {
8159       (void) StopLoadGameTimer();
8160       gameFileFP = NULL;
8161     }
8162
8163     /* Cancel draw offers */
8164     first.offeredDraw = second.offeredDraw = 0;
8165
8166     /* If this is an ICS game, only ICS can really say it's done;
8167        if not, anyone can. */
8168     isIcsGame = (gameMode == IcsPlayingWhite || 
8169                  gameMode == IcsPlayingBlack || 
8170                  gameMode == IcsObserving    || 
8171                  gameMode == IcsExamining);
8172
8173     if (!isIcsGame || whosays == GE_ICS) {
8174         /* OK -- not an ICS game, or ICS said it was done */
8175         StopClocks();
8176         if (!isIcsGame && !appData.noChessProgram) 
8177           SetUserThinkingEnables();
8178     
8179         /* [HGM] if a machine claims the game end we verify this claim */
8180         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8181             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8182                 char claimer;
8183                 ChessMove trueResult = (ChessMove) -1;
8184
8185                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8186                                             first.twoMachinesColor[0] :
8187                                             second.twoMachinesColor[0] ;
8188
8189                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8190                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8191                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8192                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8193                 } else
8194                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8195                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8196                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8197                 } else
8198                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8199                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8200                 }
8201
8202                 // now verify win claims, but not in drop games, as we don't understand those yet
8203                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8204                                                  || gameInfo.variant == VariantGreat) &&
8205                     (result == WhiteWins && claimer == 'w' ||
8206                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8207                       if (appData.debugMode) {
8208                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8209                                 result, epStatus[forwardMostMove], forwardMostMove);
8210                       }
8211                       if(result != trueResult) {
8212                               sprintf(buf, "False win claim: '%s'", resultDetails);
8213                               result = claimer == 'w' ? BlackWins : WhiteWins;
8214                               resultDetails = buf;
8215                       }
8216                 } else
8217                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8218                     && (forwardMostMove <= backwardMostMove ||
8219                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8220                         (claimer=='b')==(forwardMostMove&1))
8221                                                                                   ) {
8222                       /* [HGM] verify: draws that were not flagged are false claims */
8223                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8224                       result = claimer == 'w' ? BlackWins : WhiteWins;
8225                       resultDetails = buf;
8226                 }
8227                 /* (Claiming a loss is accepted no questions asked!) */
8228             }
8229             /* [HGM] bare: don't allow bare King to win */
8230             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8231                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8232                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8233                && result != GameIsDrawn)
8234             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8235                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8236                         int p = (int)boards[forwardMostMove][i][j] - color;
8237                         if(p >= 0 && p <= (int)WhiteKing) k++;
8238                 }
8239                 if (appData.debugMode) {
8240                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8241                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8242                 }
8243                 if(k <= 1) {
8244                         result = GameIsDrawn;
8245                         sprintf(buf, "%s but bare king", resultDetails);
8246                         resultDetails = buf;
8247                 }
8248             }
8249         }
8250
8251
8252         if(serverMoves != NULL && !loadFlag) { char c = '=';
8253             if(result==WhiteWins) c = '+';
8254             if(result==BlackWins) c = '-';
8255             if(resultDetails != NULL)
8256                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8257         }
8258         if (resultDetails != NULL) {
8259             gameInfo.result = result;
8260             gameInfo.resultDetails = StrSave(resultDetails);
8261
8262             /* display last move only if game was not loaded from file */
8263             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8264                 DisplayMove(currentMove - 1);
8265     
8266             if (forwardMostMove != 0) {
8267                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8268                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8269                                                                 ) {
8270                     if (*appData.saveGameFile != NULLCHAR) {
8271                         SaveGameToFile(appData.saveGameFile, TRUE);
8272                     } else if (appData.autoSaveGames) {
8273                         AutoSaveGame();
8274                     }
8275                     if (*appData.savePositionFile != NULLCHAR) {
8276                         SavePositionToFile(appData.savePositionFile);
8277                     }
8278                 }
8279             }
8280
8281             /* Tell program how game ended in case it is learning */
8282             /* [HGM] Moved this to after saving the PGN, just in case */
8283             /* engine died and we got here through time loss. In that */
8284             /* case we will get a fatal error writing the pipe, which */
8285             /* would otherwise lose us the PGN.                       */
8286             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8287             /* output during GameEnds should never be fatal anymore   */
8288             if (gameMode == MachinePlaysWhite ||
8289                 gameMode == MachinePlaysBlack ||
8290                 gameMode == TwoMachinesPlay ||
8291                 gameMode == IcsPlayingWhite ||
8292                 gameMode == IcsPlayingBlack ||
8293                 gameMode == BeginningOfGame) {
8294                 char buf[MSG_SIZ];
8295                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8296                         resultDetails);
8297                 if (first.pr != NoProc) {
8298                     SendToProgram(buf, &first);
8299                 }
8300                 if (second.pr != NoProc &&
8301                     gameMode == TwoMachinesPlay) {
8302                     SendToProgram(buf, &second);
8303                 }
8304             }
8305         }
8306
8307         if (appData.icsActive) {
8308             if (appData.quietPlay &&
8309                 (gameMode == IcsPlayingWhite ||
8310                  gameMode == IcsPlayingBlack)) {
8311                 SendToICS(ics_prefix);
8312                 SendToICS("set shout 1\n");
8313             }
8314             nextGameMode = IcsIdle;
8315             ics_user_moved = FALSE;
8316             /* clean up premove.  It's ugly when the game has ended and the
8317              * premove highlights are still on the board.
8318              */
8319             if (gotPremove) {
8320               gotPremove = FALSE;
8321               ClearPremoveHighlights();
8322               DrawPosition(FALSE, boards[currentMove]);
8323             }
8324             if (whosays == GE_ICS) {
8325                 switch (result) {
8326                 case WhiteWins:
8327                     if (gameMode == IcsPlayingWhite)
8328                         PlayIcsWinSound();
8329                     else if(gameMode == IcsPlayingBlack)
8330                         PlayIcsLossSound();
8331                     break;
8332                 case BlackWins:
8333                     if (gameMode == IcsPlayingBlack)
8334                         PlayIcsWinSound();
8335                     else if(gameMode == IcsPlayingWhite)
8336                         PlayIcsLossSound();
8337                     break;
8338                 case GameIsDrawn:
8339                     PlayIcsDrawSound();
8340                     break;
8341                 default:
8342                     PlayIcsUnfinishedSound();
8343                 }
8344             }
8345         } else if (gameMode == EditGame ||
8346                    gameMode == PlayFromGameFile || 
8347                    gameMode == AnalyzeMode || 
8348                    gameMode == AnalyzeFile) {
8349             nextGameMode = gameMode;
8350         } else {
8351             nextGameMode = EndOfGame;
8352         }
8353         pausing = FALSE;
8354         ModeHighlight();
8355     } else {
8356         nextGameMode = gameMode;
8357     }
8358
8359     if (appData.noChessProgram) {
8360         gameMode = nextGameMode;
8361         ModeHighlight();
8362         endingGame = 0; /* [HGM] crash */
8363         return;
8364     }
8365
8366     if (first.reuse) {
8367         /* Put first chess program into idle state */
8368         if (first.pr != NoProc &&
8369             (gameMode == MachinePlaysWhite ||
8370              gameMode == MachinePlaysBlack ||
8371              gameMode == TwoMachinesPlay ||
8372              gameMode == IcsPlayingWhite ||
8373              gameMode == IcsPlayingBlack ||
8374              gameMode == BeginningOfGame)) {
8375             SendToProgram("force\n", &first);
8376             if (first.usePing) {
8377               char buf[MSG_SIZ];
8378               sprintf(buf, "ping %d\n", ++first.lastPing);
8379               SendToProgram(buf, &first);
8380             }
8381         }
8382     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8383         /* Kill off first chess program */
8384         if (first.isr != NULL)
8385           RemoveInputSource(first.isr);
8386         first.isr = NULL;
8387     
8388         if (first.pr != NoProc) {
8389             ExitAnalyzeMode();
8390             DoSleep( appData.delayBeforeQuit );
8391             SendToProgram("quit\n", &first);
8392             DoSleep( appData.delayAfterQuit );
8393             DestroyChildProcess(first.pr, first.useSigterm);
8394         }
8395         first.pr = NoProc;
8396     }
8397     if (second.reuse) {
8398         /* Put second chess program into idle state */
8399         if (second.pr != NoProc &&
8400             gameMode == TwoMachinesPlay) {
8401             SendToProgram("force\n", &second);
8402             if (second.usePing) {
8403               char buf[MSG_SIZ];
8404               sprintf(buf, "ping %d\n", ++second.lastPing);
8405               SendToProgram(buf, &second);
8406             }
8407         }
8408     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8409         /* Kill off second chess program */
8410         if (second.isr != NULL)
8411           RemoveInputSource(second.isr);
8412         second.isr = NULL;
8413     
8414         if (second.pr != NoProc) {
8415             DoSleep( appData.delayBeforeQuit );
8416             SendToProgram("quit\n", &second);
8417             DoSleep( appData.delayAfterQuit );
8418             DestroyChildProcess(second.pr, second.useSigterm);
8419         }
8420         second.pr = NoProc;
8421     }
8422
8423     if (matchMode && gameMode == TwoMachinesPlay) {
8424         switch (result) {
8425         case WhiteWins:
8426           if (first.twoMachinesColor[0] == 'w') {
8427             first.matchWins++;
8428           } else {
8429             second.matchWins++;
8430           }
8431           break;
8432         case BlackWins:
8433           if (first.twoMachinesColor[0] == 'b') {
8434             first.matchWins++;
8435           } else {
8436             second.matchWins++;
8437           }
8438           break;
8439         default:
8440           break;
8441         }
8442         if (matchGame < appData.matchGames) {
8443             char *tmp;
8444             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8445                 tmp = first.twoMachinesColor;
8446                 first.twoMachinesColor = second.twoMachinesColor;
8447                 second.twoMachinesColor = tmp;
8448             }
8449             gameMode = nextGameMode;
8450             matchGame++;
8451             if(appData.matchPause>10000 || appData.matchPause<10)
8452                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8453             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8454             endingGame = 0; /* [HGM] crash */
8455             return;
8456         } else {
8457             char buf[MSG_SIZ];
8458             gameMode = nextGameMode;
8459             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8460                     first.tidy, second.tidy,
8461                     first.matchWins, second.matchWins,
8462                     appData.matchGames - (first.matchWins + second.matchWins));
8463             DisplayFatalError(buf, 0, 0);
8464         }
8465     }
8466     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8467         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8468       ExitAnalyzeMode();
8469     gameMode = nextGameMode;
8470     ModeHighlight();
8471     endingGame = 0;  /* [HGM] crash */
8472 }
8473
8474 /* Assumes program was just initialized (initString sent).
8475    Leaves program in force mode. */
8476 void
8477 FeedMovesToProgram(cps, upto) 
8478      ChessProgramState *cps;
8479      int upto;
8480 {
8481     int i;
8482     
8483     if (appData.debugMode)
8484       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8485               startedFromSetupPosition ? "position and " : "",
8486               backwardMostMove, upto, cps->which);
8487     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8488         // [HGM] variantswitch: make engine aware of new variant
8489         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8490                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8491         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8492         SendToProgram(buf, cps);
8493         currentlyInitializedVariant = gameInfo.variant;
8494     }
8495     SendToProgram("force\n", cps);
8496     if (startedFromSetupPosition) {
8497         SendBoard(cps, backwardMostMove);
8498     if (appData.debugMode) {
8499         fprintf(debugFP, "feedMoves\n");
8500     }
8501     }
8502     for (i = backwardMostMove; i < upto; i++) {
8503         SendMoveToProgram(i, cps);
8504     }
8505 }
8506
8507
8508 void
8509 ResurrectChessProgram()
8510 {
8511      /* The chess program may have exited.
8512         If so, restart it and feed it all the moves made so far. */
8513
8514     if (appData.noChessProgram || first.pr != NoProc) return;
8515     
8516     StartChessProgram(&first);
8517     InitChessProgram(&first, FALSE);
8518     FeedMovesToProgram(&first, currentMove);
8519
8520     if (!first.sendTime) {
8521         /* can't tell gnuchess what its clock should read,
8522            so we bow to its notion. */
8523         ResetClocks();
8524         timeRemaining[0][currentMove] = whiteTimeRemaining;
8525         timeRemaining[1][currentMove] = blackTimeRemaining;
8526     }
8527
8528     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8529                 appData.icsEngineAnalyze) && first.analysisSupport) {
8530       SendToProgram("analyze\n", &first);
8531       first.analyzing = TRUE;
8532     }
8533 }
8534
8535 /*
8536  * Button procedures
8537  */
8538 void
8539 Reset(redraw, init)
8540      int redraw, init;
8541 {
8542     int i;
8543
8544     if (appData.debugMode) {
8545         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8546                 redraw, init, gameMode);
8547     }
8548     pausing = pauseExamInvalid = FALSE;
8549     startedFromSetupPosition = blackPlaysFirst = FALSE;
8550     firstMove = TRUE;
8551     whiteFlag = blackFlag = FALSE;
8552     userOfferedDraw = FALSE;
8553     hintRequested = bookRequested = FALSE;
8554     first.maybeThinking = FALSE;
8555     second.maybeThinking = FALSE;
8556     first.bookSuspend = FALSE; // [HGM] book
8557     second.bookSuspend = FALSE;
8558     thinkOutput[0] = NULLCHAR;
8559     lastHint[0] = NULLCHAR;
8560     ClearGameInfo(&gameInfo);
8561     gameInfo.variant = StringToVariant(appData.variant);
8562     ics_user_moved = ics_clock_paused = FALSE;
8563     ics_getting_history = H_FALSE;
8564     ics_gamenum = -1;
8565     white_holding[0] = black_holding[0] = NULLCHAR;
8566     ClearProgramStats();
8567     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8568     
8569     ResetFrontEnd();
8570     ClearHighlights();
8571     flipView = appData.flipView;
8572     ClearPremoveHighlights();
8573     gotPremove = FALSE;
8574     alarmSounded = FALSE;
8575
8576     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8577     if(appData.serverMovesName != NULL) {
8578         /* [HGM] prepare to make moves file for broadcasting */
8579         clock_t t = clock();
8580         if(serverMoves != NULL) fclose(serverMoves);
8581         serverMoves = fopen(appData.serverMovesName, "r");
8582         if(serverMoves != NULL) {
8583             fclose(serverMoves);
8584             /* delay 15 sec before overwriting, so all clients can see end */
8585             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8586         }
8587         serverMoves = fopen(appData.serverMovesName, "w");
8588     }
8589
8590     ExitAnalyzeMode();
8591     gameMode = BeginningOfGame;
8592     ModeHighlight();
8593     if(appData.icsActive) gameInfo.variant = VariantNormal;
8594     currentMove = forwardMostMove = backwardMostMove = 0;
8595     InitPosition(redraw);
8596     for (i = 0; i < MAX_MOVES; i++) {
8597         if (commentList[i] != NULL) {
8598             free(commentList[i]);
8599             commentList[i] = NULL;
8600         }
8601     }
8602     ResetClocks();
8603     timeRemaining[0][0] = whiteTimeRemaining;
8604     timeRemaining[1][0] = blackTimeRemaining;
8605     if (first.pr == NULL) {
8606         StartChessProgram(&first);
8607     }
8608     if (init) {
8609             InitChessProgram(&first, startedFromSetupPosition);
8610     }
8611     DisplayTitle("");
8612     DisplayMessage("", "");
8613     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8614     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8615 }
8616
8617 void
8618 AutoPlayGameLoop()
8619 {
8620     for (;;) {
8621         if (!AutoPlayOneMove())
8622           return;
8623         if (matchMode || appData.timeDelay == 0)
8624           continue;
8625         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8626           return;
8627         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8628         break;
8629     }
8630 }
8631
8632
8633 int
8634 AutoPlayOneMove()
8635 {
8636     int fromX, fromY, toX, toY;
8637
8638     if (appData.debugMode) {
8639       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8640     }
8641
8642     if (gameMode != PlayFromGameFile)
8643       return FALSE;
8644
8645     if (currentMove >= forwardMostMove) {
8646       gameMode = EditGame;
8647       ModeHighlight();
8648
8649       /* [AS] Clear current move marker at the end of a game */
8650       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8651
8652       return FALSE;
8653     }
8654     
8655     toX = moveList[currentMove][2] - AAA;
8656     toY = moveList[currentMove][3] - ONE;
8657
8658     if (moveList[currentMove][1] == '@') {
8659         if (appData.highlightLastMove) {
8660             SetHighlights(-1, -1, toX, toY);
8661         }
8662     } else {
8663         fromX = moveList[currentMove][0] - AAA;
8664         fromY = moveList[currentMove][1] - ONE;
8665
8666         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8667
8668         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8669
8670         if (appData.highlightLastMove) {
8671             SetHighlights(fromX, fromY, toX, toY);
8672         }
8673     }
8674     DisplayMove(currentMove);
8675     SendMoveToProgram(currentMove++, &first);
8676     DisplayBothClocks();
8677     DrawPosition(FALSE, boards[currentMove]);
8678     // [HGM] PV info: always display, routine tests if empty
8679     DisplayComment(currentMove - 1, commentList[currentMove]);
8680     return TRUE;
8681 }
8682
8683
8684 int
8685 LoadGameOneMove(readAhead)
8686      ChessMove readAhead;
8687 {
8688     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8689     char promoChar = NULLCHAR;
8690     ChessMove moveType;
8691     char move[MSG_SIZ];
8692     char *p, *q;
8693     
8694     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8695         gameMode != AnalyzeMode && gameMode != Training) {
8696         gameFileFP = NULL;
8697         return FALSE;
8698     }
8699     
8700     yyboardindex = forwardMostMove;
8701     if (readAhead != (ChessMove)0) {
8702       moveType = readAhead;
8703     } else {
8704       if (gameFileFP == NULL)
8705           return FALSE;
8706       moveType = (ChessMove) yylex();
8707     }
8708     
8709     done = FALSE;
8710     switch (moveType) {
8711       case Comment:
8712         if (appData.debugMode) 
8713           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8714         p = yy_text;
8715         if (*p == '{' || *p == '[' || *p == '(') {
8716             p[strlen(p) - 1] = NULLCHAR;
8717             p++;
8718         }
8719
8720         /* append the comment but don't display it */
8721         while (*p == '\n') p++;
8722         AppendComment(currentMove, p);
8723         return TRUE;
8724
8725       case WhiteCapturesEnPassant:
8726       case BlackCapturesEnPassant:
8727       case WhitePromotionChancellor:
8728       case BlackPromotionChancellor:
8729       case WhitePromotionArchbishop:
8730       case BlackPromotionArchbishop:
8731       case WhitePromotionCentaur:
8732       case BlackPromotionCentaur:
8733       case WhitePromotionQueen:
8734       case BlackPromotionQueen:
8735       case WhitePromotionRook:
8736       case BlackPromotionRook:
8737       case WhitePromotionBishop:
8738       case BlackPromotionBishop:
8739       case WhitePromotionKnight:
8740       case BlackPromotionKnight:
8741       case WhitePromotionKing:
8742       case BlackPromotionKing:
8743       case NormalMove:
8744       case WhiteKingSideCastle:
8745       case WhiteQueenSideCastle:
8746       case BlackKingSideCastle:
8747       case BlackQueenSideCastle:
8748       case WhiteKingSideCastleWild:
8749       case WhiteQueenSideCastleWild:
8750       case BlackKingSideCastleWild:
8751       case BlackQueenSideCastleWild:
8752       /* PUSH Fabien */
8753       case WhiteHSideCastleFR:
8754       case WhiteASideCastleFR:
8755       case BlackHSideCastleFR:
8756       case BlackASideCastleFR:
8757       /* POP Fabien */
8758         if (appData.debugMode)
8759           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8760         fromX = currentMoveString[0] - AAA;
8761         fromY = currentMoveString[1] - ONE;
8762         toX = currentMoveString[2] - AAA;
8763         toY = currentMoveString[3] - ONE;
8764         promoChar = currentMoveString[4];
8765         break;
8766
8767       case WhiteDrop:
8768       case BlackDrop:
8769         if (appData.debugMode)
8770           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8771         fromX = moveType == WhiteDrop ?
8772           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8773         (int) CharToPiece(ToLower(currentMoveString[0]));
8774         fromY = DROP_RANK;
8775         toX = currentMoveString[2] - AAA;
8776         toY = currentMoveString[3] - ONE;
8777         break;
8778
8779       case WhiteWins:
8780       case BlackWins:
8781       case GameIsDrawn:
8782       case GameUnfinished:
8783         if (appData.debugMode)
8784           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8785         p = strchr(yy_text, '{');
8786         if (p == NULL) p = strchr(yy_text, '(');
8787         if (p == NULL) {
8788             p = yy_text;
8789             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8790         } else {
8791             q = strchr(p, *p == '{' ? '}' : ')');
8792             if (q != NULL) *q = NULLCHAR;
8793             p++;
8794         }
8795         GameEnds(moveType, p, GE_FILE);
8796         done = TRUE;
8797         if (cmailMsgLoaded) {
8798             ClearHighlights();
8799             flipView = WhiteOnMove(currentMove);
8800             if (moveType == GameUnfinished) flipView = !flipView;
8801             if (appData.debugMode)
8802               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8803         }
8804         break;
8805
8806       case (ChessMove) 0:       /* end of file */
8807         if (appData.debugMode)
8808           fprintf(debugFP, "Parser hit end of file\n");
8809         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8810                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8811           case MT_NONE:
8812           case MT_CHECK:
8813             break;
8814           case MT_CHECKMATE:
8815           case MT_STAINMATE:
8816             if (WhiteOnMove(currentMove)) {
8817                 GameEnds(BlackWins, "Black mates", GE_FILE);
8818             } else {
8819                 GameEnds(WhiteWins, "White mates", GE_FILE);
8820             }
8821             break;
8822           case MT_STALEMATE:
8823             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8824             break;
8825         }
8826         done = TRUE;
8827         break;
8828
8829       case MoveNumberOne:
8830         if (lastLoadGameStart == GNUChessGame) {
8831             /* GNUChessGames have numbers, but they aren't move numbers */
8832             if (appData.debugMode)
8833               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8834                       yy_text, (int) moveType);
8835             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8836         }
8837         /* else fall thru */
8838
8839       case XBoardGame:
8840       case GNUChessGame:
8841       case PGNTag:
8842         /* Reached start of next game in file */
8843         if (appData.debugMode)
8844           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8845         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8846                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8847           case MT_NONE:
8848           case MT_CHECK:
8849             break;
8850           case MT_CHECKMATE:
8851           case MT_STAINMATE:
8852             if (WhiteOnMove(currentMove)) {
8853                 GameEnds(BlackWins, "Black mates", GE_FILE);
8854             } else {
8855                 GameEnds(WhiteWins, "White mates", GE_FILE);
8856             }
8857             break;
8858           case MT_STALEMATE:
8859             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8860             break;
8861         }
8862         done = TRUE;
8863         break;
8864
8865       case PositionDiagram:     /* should not happen; ignore */
8866       case ElapsedTime:         /* ignore */
8867       case NAG:                 /* ignore */
8868         if (appData.debugMode)
8869           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8870                   yy_text, (int) moveType);
8871         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8872
8873       case IllegalMove:
8874         if (appData.testLegality) {
8875             if (appData.debugMode)
8876               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8877             sprintf(move, _("Illegal move: %d.%s%s"),
8878                     (forwardMostMove / 2) + 1,
8879                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8880             DisplayError(move, 0);
8881             done = TRUE;
8882         } else {
8883             if (appData.debugMode)
8884               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8885                       yy_text, currentMoveString);
8886             fromX = currentMoveString[0] - AAA;
8887             fromY = currentMoveString[1] - ONE;
8888             toX = currentMoveString[2] - AAA;
8889             toY = currentMoveString[3] - ONE;
8890             promoChar = currentMoveString[4];
8891         }
8892         break;
8893
8894       case AmbiguousMove:
8895         if (appData.debugMode)
8896           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8897         sprintf(move, _("Ambiguous move: %d.%s%s"),
8898                 (forwardMostMove / 2) + 1,
8899                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8900         DisplayError(move, 0);
8901         done = TRUE;
8902         break;
8903
8904       default:
8905       case ImpossibleMove:
8906         if (appData.debugMode)
8907           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8908         sprintf(move, _("Illegal move: %d.%s%s"),
8909                 (forwardMostMove / 2) + 1,
8910                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8911         DisplayError(move, 0);
8912         done = TRUE;
8913         break;
8914     }
8915
8916     if (done) {
8917         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8918             DrawPosition(FALSE, boards[currentMove]);
8919             DisplayBothClocks();
8920             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8921               DisplayComment(currentMove - 1, commentList[currentMove]);
8922         }
8923         (void) StopLoadGameTimer();
8924         gameFileFP = NULL;
8925         cmailOldMove = forwardMostMove;
8926         return FALSE;
8927     } else {
8928         /* currentMoveString is set as a side-effect of yylex */
8929         strcat(currentMoveString, "\n");
8930         strcpy(moveList[forwardMostMove], currentMoveString);
8931         
8932         thinkOutput[0] = NULLCHAR;
8933         MakeMove(fromX, fromY, toX, toY, promoChar);
8934         currentMove = forwardMostMove;
8935         return TRUE;
8936     }
8937 }
8938
8939 /* Load the nth game from the given file */
8940 int
8941 LoadGameFromFile(filename, n, title, useList)
8942      char *filename;
8943      int n;
8944      char *title;
8945      /*Boolean*/ int useList;
8946 {
8947     FILE *f;
8948     char buf[MSG_SIZ];
8949
8950     if (strcmp(filename, "-") == 0) {
8951         f = stdin;
8952         title = "stdin";
8953     } else {
8954         f = fopen(filename, "rb");
8955         if (f == NULL) {
8956           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8957             DisplayError(buf, errno);
8958             return FALSE;
8959         }
8960     }
8961     if (fseek(f, 0, 0) == -1) {
8962         /* f is not seekable; probably a pipe */
8963         useList = FALSE;
8964     }
8965     if (useList && n == 0) {
8966         int error = GameListBuild(f);
8967         if (error) {
8968             DisplayError(_("Cannot build game list"), error);
8969         } else if (!ListEmpty(&gameList) &&
8970                    ((ListGame *) gameList.tailPred)->number > 1) {
8971             GameListPopUp(f, title);
8972             return TRUE;
8973         }
8974         GameListDestroy();
8975         n = 1;
8976     }
8977     if (n == 0) n = 1;
8978     return LoadGame(f, n, title, FALSE);
8979 }
8980
8981
8982 void
8983 MakeRegisteredMove()
8984 {
8985     int fromX, fromY, toX, toY;
8986     char promoChar;
8987     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8988         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8989           case CMAIL_MOVE:
8990           case CMAIL_DRAW:
8991             if (appData.debugMode)
8992               fprintf(debugFP, "Restoring %s for game %d\n",
8993                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8994     
8995             thinkOutput[0] = NULLCHAR;
8996             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8997             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8998             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8999             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9000             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9001             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9002             MakeMove(fromX, fromY, toX, toY, promoChar);
9003             ShowMove(fromX, fromY, toX, toY);
9004               
9005             switch (MateTest(boards[currentMove], PosFlags(currentMove),
9006                              EP_UNKNOWN, castlingRights[currentMove]) ) {
9007               case MT_NONE:
9008               case MT_CHECK:
9009                 break;
9010                 
9011               case MT_CHECKMATE:
9012               case MT_STAINMATE:
9013                 if (WhiteOnMove(currentMove)) {
9014                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9015                 } else {
9016                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9017                 }
9018                 break;
9019                 
9020               case MT_STALEMATE:
9021                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9022                 break;
9023             }
9024
9025             break;
9026             
9027           case CMAIL_RESIGN:
9028             if (WhiteOnMove(currentMove)) {
9029                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9030             } else {
9031                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9032             }
9033             break;
9034             
9035           case CMAIL_ACCEPT:
9036             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9037             break;
9038               
9039           default:
9040             break;
9041         }
9042     }
9043
9044     return;
9045 }
9046
9047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9048 int
9049 CmailLoadGame(f, gameNumber, title, useList)
9050      FILE *f;
9051      int gameNumber;
9052      char *title;
9053      int useList;
9054 {
9055     int retVal;
9056
9057     if (gameNumber > nCmailGames) {
9058         DisplayError(_("No more games in this message"), 0);
9059         return FALSE;
9060     }
9061     if (f == lastLoadGameFP) {
9062         int offset = gameNumber - lastLoadGameNumber;
9063         if (offset == 0) {
9064             cmailMsg[0] = NULLCHAR;
9065             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9066                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9067                 nCmailMovesRegistered--;
9068             }
9069             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9070             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9071                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9072             }
9073         } else {
9074             if (! RegisterMove()) return FALSE;
9075         }
9076     }
9077
9078     retVal = LoadGame(f, gameNumber, title, useList);
9079
9080     /* Make move registered during previous look at this game, if any */
9081     MakeRegisteredMove();
9082
9083     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9084         commentList[currentMove]
9085           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9086         DisplayComment(currentMove - 1, commentList[currentMove]);
9087     }
9088
9089     return retVal;
9090 }
9091
9092 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9093 int
9094 ReloadGame(offset)
9095      int offset;
9096 {
9097     int gameNumber = lastLoadGameNumber + offset;
9098     if (lastLoadGameFP == NULL) {
9099         DisplayError(_("No game has been loaded yet"), 0);
9100         return FALSE;
9101     }
9102     if (gameNumber <= 0) {
9103         DisplayError(_("Can't back up any further"), 0);
9104         return FALSE;
9105     }
9106     if (cmailMsgLoaded) {
9107         return CmailLoadGame(lastLoadGameFP, gameNumber,
9108                              lastLoadGameTitle, lastLoadGameUseList);
9109     } else {
9110         return LoadGame(lastLoadGameFP, gameNumber,
9111                         lastLoadGameTitle, lastLoadGameUseList);
9112     }
9113 }
9114
9115
9116
9117 /* Load the nth game from open file f */
9118 int
9119 LoadGame(f, gameNumber, title, useList)
9120      FILE *f;
9121      int gameNumber;
9122      char *title;
9123      int useList;
9124 {
9125     ChessMove cm;
9126     char buf[MSG_SIZ];
9127     int gn = gameNumber;
9128     ListGame *lg = NULL;
9129     int numPGNTags = 0;
9130     int err;
9131     GameMode oldGameMode;
9132     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9133
9134     if (appData.debugMode) 
9135         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9136
9137     if (gameMode == Training )
9138         SetTrainingModeOff();
9139
9140     oldGameMode = gameMode;
9141     if (gameMode != BeginningOfGame) {
9142       Reset(FALSE, TRUE);
9143     }
9144
9145     gameFileFP = f;
9146     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9147         fclose(lastLoadGameFP);
9148     }
9149
9150     if (useList) {
9151         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9152         
9153         if (lg) {
9154             fseek(f, lg->offset, 0);
9155             GameListHighlight(gameNumber);
9156             gn = 1;
9157         }
9158         else {
9159             DisplayError(_("Game number out of range"), 0);
9160             return FALSE;
9161         }
9162     } else {
9163         GameListDestroy();
9164         if (fseek(f, 0, 0) == -1) {
9165             if (f == lastLoadGameFP ?
9166                 gameNumber == lastLoadGameNumber + 1 :
9167                 gameNumber == 1) {
9168                 gn = 1;
9169             } else {
9170                 DisplayError(_("Can't seek on game file"), 0);
9171                 return FALSE;
9172             }
9173         }
9174     }
9175     lastLoadGameFP = f;
9176     lastLoadGameNumber = gameNumber;
9177     strcpy(lastLoadGameTitle, title);
9178     lastLoadGameUseList = useList;
9179
9180     yynewfile(f);
9181
9182     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9183       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9184                 lg->gameInfo.black);
9185             DisplayTitle(buf);
9186     } else if (*title != NULLCHAR) {
9187         if (gameNumber > 1) {
9188             sprintf(buf, "%s %d", title, gameNumber);
9189             DisplayTitle(buf);
9190         } else {
9191             DisplayTitle(title);
9192         }
9193     }
9194
9195     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9196         gameMode = PlayFromGameFile;
9197         ModeHighlight();
9198     }
9199
9200     currentMove = forwardMostMove = backwardMostMove = 0;
9201     CopyBoard(boards[0], initialPosition);
9202     StopClocks();
9203
9204     /*
9205      * Skip the first gn-1 games in the file.
9206      * Also skip over anything that precedes an identifiable 
9207      * start of game marker, to avoid being confused by 
9208      * garbage at the start of the file.  Currently 
9209      * recognized start of game markers are the move number "1",
9210      * the pattern "gnuchess .* game", the pattern
9211      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9212      * A game that starts with one of the latter two patterns
9213      * will also have a move number 1, possibly
9214      * following a position diagram.
9215      * 5-4-02: Let's try being more lenient and allowing a game to
9216      * start with an unnumbered move.  Does that break anything?
9217      */
9218     cm = lastLoadGameStart = (ChessMove) 0;
9219     while (gn > 0) {
9220         yyboardindex = forwardMostMove;
9221         cm = (ChessMove) yylex();
9222         switch (cm) {
9223           case (ChessMove) 0:
9224             if (cmailMsgLoaded) {
9225                 nCmailGames = CMAIL_MAX_GAMES - gn;
9226             } else {
9227                 Reset(TRUE, TRUE);
9228                 DisplayError(_("Game not found in file"), 0);
9229             }
9230             return FALSE;
9231
9232           case GNUChessGame:
9233           case XBoardGame:
9234             gn--;
9235             lastLoadGameStart = cm;
9236             break;
9237             
9238           case MoveNumberOne:
9239             switch (lastLoadGameStart) {
9240               case GNUChessGame:
9241               case XBoardGame:
9242               case PGNTag:
9243                 break;
9244               case MoveNumberOne:
9245               case (ChessMove) 0:
9246                 gn--;           /* count this game */
9247                 lastLoadGameStart = cm;
9248                 break;
9249               default:
9250                 /* impossible */
9251                 break;
9252             }
9253             break;
9254
9255           case PGNTag:
9256             switch (lastLoadGameStart) {
9257               case GNUChessGame:
9258               case PGNTag:
9259               case MoveNumberOne:
9260               case (ChessMove) 0:
9261                 gn--;           /* count this game */
9262                 lastLoadGameStart = cm;
9263                 break;
9264               case XBoardGame:
9265                 lastLoadGameStart = cm; /* game counted already */
9266                 break;
9267               default:
9268                 /* impossible */
9269                 break;
9270             }
9271             if (gn > 0) {
9272                 do {
9273                     yyboardindex = forwardMostMove;
9274                     cm = (ChessMove) yylex();
9275                 } while (cm == PGNTag || cm == Comment);
9276             }
9277             break;
9278
9279           case WhiteWins:
9280           case BlackWins:
9281           case GameIsDrawn:
9282             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9283                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9284                     != CMAIL_OLD_RESULT) {
9285                     nCmailResults ++ ;
9286                     cmailResult[  CMAIL_MAX_GAMES
9287                                 - gn - 1] = CMAIL_OLD_RESULT;
9288                 }
9289             }
9290             break;
9291
9292           case NormalMove:
9293             /* Only a NormalMove can be at the start of a game
9294              * without a position diagram. */
9295             if (lastLoadGameStart == (ChessMove) 0) {
9296               gn--;
9297               lastLoadGameStart = MoveNumberOne;
9298             }
9299             break;
9300
9301           default:
9302             break;
9303         }
9304     }
9305     
9306     if (appData.debugMode)
9307       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9308
9309     if (cm == XBoardGame) {
9310         /* Skip any header junk before position diagram and/or move 1 */
9311         for (;;) {
9312             yyboardindex = forwardMostMove;
9313             cm = (ChessMove) yylex();
9314
9315             if (cm == (ChessMove) 0 ||
9316                 cm == GNUChessGame || cm == XBoardGame) {
9317                 /* Empty game; pretend end-of-file and handle later */
9318                 cm = (ChessMove) 0;
9319                 break;
9320             }
9321
9322             if (cm == MoveNumberOne || cm == PositionDiagram ||
9323                 cm == PGNTag || cm == Comment)
9324               break;
9325         }
9326     } else if (cm == GNUChessGame) {
9327         if (gameInfo.event != NULL) {
9328             free(gameInfo.event);
9329         }
9330         gameInfo.event = StrSave(yy_text);
9331     }   
9332
9333     startedFromSetupPosition = FALSE;
9334     while (cm == PGNTag) {
9335         if (appData.debugMode) 
9336           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9337         err = ParsePGNTag(yy_text, &gameInfo);
9338         if (!err) numPGNTags++;
9339
9340         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9341         if(gameInfo.variant != oldVariant) {
9342             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9343             InitPosition(TRUE);
9344             oldVariant = gameInfo.variant;
9345             if (appData.debugMode) 
9346               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9347         }
9348
9349
9350         if (gameInfo.fen != NULL) {
9351           Board initial_position;
9352           startedFromSetupPosition = TRUE;
9353           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9354             Reset(TRUE, TRUE);
9355             DisplayError(_("Bad FEN position in file"), 0);
9356             return FALSE;
9357           }
9358           CopyBoard(boards[0], initial_position);
9359           if (blackPlaysFirst) {
9360             currentMove = forwardMostMove = backwardMostMove = 1;
9361             CopyBoard(boards[1], initial_position);
9362             strcpy(moveList[0], "");
9363             strcpy(parseList[0], "");
9364             timeRemaining[0][1] = whiteTimeRemaining;
9365             timeRemaining[1][1] = blackTimeRemaining;
9366             if (commentList[0] != NULL) {
9367               commentList[1] = commentList[0];
9368               commentList[0] = NULL;
9369             }
9370           } else {
9371             currentMove = forwardMostMove = backwardMostMove = 0;
9372           }
9373           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9374           {   int i;
9375               initialRulePlies = FENrulePlies;
9376               epStatus[forwardMostMove] = FENepStatus;
9377               for( i=0; i< nrCastlingRights; i++ )
9378                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9379           }
9380           yyboardindex = forwardMostMove;
9381           free(gameInfo.fen);
9382           gameInfo.fen = NULL;
9383         }
9384
9385         yyboardindex = forwardMostMove;
9386         cm = (ChessMove) yylex();
9387
9388         /* Handle comments interspersed among the tags */
9389         while (cm == Comment) {
9390             char *p;
9391             if (appData.debugMode) 
9392               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9393             p = yy_text;
9394             if (*p == '{' || *p == '[' || *p == '(') {
9395                 p[strlen(p) - 1] = NULLCHAR;
9396                 p++;
9397             }
9398             while (*p == '\n') p++;
9399             AppendComment(currentMove, p);
9400             yyboardindex = forwardMostMove;
9401             cm = (ChessMove) yylex();
9402         }
9403     }
9404
9405     /* don't rely on existence of Event tag since if game was
9406      * pasted from clipboard the Event tag may not exist
9407      */
9408     if (numPGNTags > 0){
9409         char *tags;
9410         if (gameInfo.variant == VariantNormal) {
9411           gameInfo.variant = StringToVariant(gameInfo.event);
9412         }
9413         if (!matchMode) {
9414           if( appData.autoDisplayTags ) {
9415             tags = PGNTags(&gameInfo);
9416             TagsPopUp(tags, CmailMsg());
9417             free(tags);
9418           }
9419         }
9420     } else {
9421         /* Make something up, but don't display it now */
9422         SetGameInfo();
9423         TagsPopDown();
9424     }
9425
9426     if (cm == PositionDiagram) {
9427         int i, j;
9428         char *p;
9429         Board initial_position;
9430
9431         if (appData.debugMode)
9432           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9433
9434         if (!startedFromSetupPosition) {
9435             p = yy_text;
9436             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9437               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9438                 switch (*p) {
9439                   case '[':
9440                   case '-':
9441                   case ' ':
9442                   case '\t':
9443                   case '\n':
9444                   case '\r':
9445                     break;
9446                   default:
9447                     initial_position[i][j++] = CharToPiece(*p);
9448                     break;
9449                 }
9450             while (*p == ' ' || *p == '\t' ||
9451                    *p == '\n' || *p == '\r') p++;
9452         
9453             if (strncmp(p, "black", strlen("black"))==0)
9454               blackPlaysFirst = TRUE;
9455             else
9456               blackPlaysFirst = FALSE;
9457             startedFromSetupPosition = TRUE;
9458         
9459             CopyBoard(boards[0], initial_position);
9460             if (blackPlaysFirst) {
9461                 currentMove = forwardMostMove = backwardMostMove = 1;
9462                 CopyBoard(boards[1], initial_position);
9463                 strcpy(moveList[0], "");
9464                 strcpy(parseList[0], "");
9465                 timeRemaining[0][1] = whiteTimeRemaining;
9466                 timeRemaining[1][1] = blackTimeRemaining;
9467                 if (commentList[0] != NULL) {
9468                     commentList[1] = commentList[0];
9469                     commentList[0] = NULL;
9470                 }
9471             } else {
9472                 currentMove = forwardMostMove = backwardMostMove = 0;
9473             }
9474         }
9475         yyboardindex = forwardMostMove;
9476         cm = (ChessMove) yylex();
9477     }
9478
9479     if (first.pr == NoProc) {
9480         StartChessProgram(&first);
9481     }
9482     InitChessProgram(&first, FALSE);
9483     SendToProgram("force\n", &first);
9484     if (startedFromSetupPosition) {
9485         SendBoard(&first, forwardMostMove);
9486     if (appData.debugMode) {
9487         fprintf(debugFP, "Load Game\n");
9488     }
9489         DisplayBothClocks();
9490     }      
9491
9492     /* [HGM] server: flag to write setup moves in broadcast file as one */
9493     loadFlag = appData.suppressLoadMoves;
9494
9495     while (cm == Comment) {
9496         char *p;
9497         if (appData.debugMode) 
9498           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9499         p = yy_text;
9500         if (*p == '{' || *p == '[' || *p == '(') {
9501             p[strlen(p) - 1] = NULLCHAR;
9502             p++;
9503         }
9504         while (*p == '\n') p++;
9505         AppendComment(currentMove, p);
9506         yyboardindex = forwardMostMove;
9507         cm = (ChessMove) yylex();
9508     }
9509
9510     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9511         cm == WhiteWins || cm == BlackWins ||
9512         cm == GameIsDrawn || cm == GameUnfinished) {
9513         DisplayMessage("", _("No moves in game"));
9514         if (cmailMsgLoaded) {
9515             if (appData.debugMode)
9516               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9517             ClearHighlights();
9518             flipView = FALSE;
9519         }
9520         DrawPosition(FALSE, boards[currentMove]);
9521         DisplayBothClocks();
9522         gameMode = EditGame;
9523         ModeHighlight();
9524         gameFileFP = NULL;
9525         cmailOldMove = 0;
9526         return TRUE;
9527     }
9528
9529     // [HGM] PV info: routine tests if comment empty
9530     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9531         DisplayComment(currentMove - 1, commentList[currentMove]);
9532     }
9533     if (!matchMode && appData.timeDelay != 0) 
9534       DrawPosition(FALSE, boards[currentMove]);
9535
9536     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9537       programStats.ok_to_send = 1;
9538     }
9539
9540     /* if the first token after the PGN tags is a move
9541      * and not move number 1, retrieve it from the parser 
9542      */
9543     if (cm != MoveNumberOne)
9544         LoadGameOneMove(cm);
9545
9546     /* load the remaining moves from the file */
9547     while (LoadGameOneMove((ChessMove)0)) {
9548       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9549       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9550     }
9551
9552     /* rewind to the start of the game */
9553     currentMove = backwardMostMove;
9554
9555     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9556
9557     if (oldGameMode == AnalyzeFile ||
9558         oldGameMode == AnalyzeMode) {
9559       AnalyzeFileEvent();
9560     }
9561
9562     if (matchMode || appData.timeDelay == 0) {
9563       ToEndEvent();
9564       gameMode = EditGame;
9565       ModeHighlight();
9566     } else if (appData.timeDelay > 0) {
9567       AutoPlayGameLoop();
9568     }
9569
9570     if (appData.debugMode) 
9571         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9572
9573     loadFlag = 0; /* [HGM] true game starts */
9574     return TRUE;
9575 }
9576
9577 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9578 int
9579 ReloadPosition(offset)
9580      int offset;
9581 {
9582     int positionNumber = lastLoadPositionNumber + offset;
9583     if (lastLoadPositionFP == NULL) {
9584         DisplayError(_("No position has been loaded yet"), 0);
9585         return FALSE;
9586     }
9587     if (positionNumber <= 0) {
9588         DisplayError(_("Can't back up any further"), 0);
9589         return FALSE;
9590     }
9591     return LoadPosition(lastLoadPositionFP, positionNumber,
9592                         lastLoadPositionTitle);
9593 }
9594
9595 /* Load the nth position from the given file */
9596 int
9597 LoadPositionFromFile(filename, n, title)
9598      char *filename;
9599      int n;
9600      char *title;
9601 {
9602     FILE *f;
9603     char buf[MSG_SIZ];
9604
9605     if (strcmp(filename, "-") == 0) {
9606         return LoadPosition(stdin, n, "stdin");
9607     } else {
9608         f = fopen(filename, "rb");
9609         if (f == NULL) {
9610             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9611             DisplayError(buf, errno);
9612             return FALSE;
9613         } else {
9614             return LoadPosition(f, n, title);
9615         }
9616     }
9617 }
9618
9619 /* Load the nth position from the given open file, and close it */
9620 int
9621 LoadPosition(f, positionNumber, title)
9622      FILE *f;
9623      int positionNumber;
9624      char *title;
9625 {
9626     char *p, line[MSG_SIZ];
9627     Board initial_position;
9628     int i, j, fenMode, pn;
9629     
9630     if (gameMode == Training )
9631         SetTrainingModeOff();
9632
9633     if (gameMode != BeginningOfGame) {
9634         Reset(FALSE, TRUE);
9635     }
9636     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9637         fclose(lastLoadPositionFP);
9638     }
9639     if (positionNumber == 0) positionNumber = 1;
9640     lastLoadPositionFP = f;
9641     lastLoadPositionNumber = positionNumber;
9642     strcpy(lastLoadPositionTitle, title);
9643     if (first.pr == NoProc) {
9644       StartChessProgram(&first);
9645       InitChessProgram(&first, FALSE);
9646     }    
9647     pn = positionNumber;
9648     if (positionNumber < 0) {
9649         /* Negative position number means to seek to that byte offset */
9650         if (fseek(f, -positionNumber, 0) == -1) {
9651             DisplayError(_("Can't seek on position file"), 0);
9652             return FALSE;
9653         };
9654         pn = 1;
9655     } else {
9656         if (fseek(f, 0, 0) == -1) {
9657             if (f == lastLoadPositionFP ?
9658                 positionNumber == lastLoadPositionNumber + 1 :
9659                 positionNumber == 1) {
9660                 pn = 1;
9661             } else {
9662                 DisplayError(_("Can't seek on position file"), 0);
9663                 return FALSE;
9664             }
9665         }
9666     }
9667     /* See if this file is FEN or old-style xboard */
9668     if (fgets(line, MSG_SIZ, f) == NULL) {
9669         DisplayError(_("Position not found in file"), 0);
9670         return FALSE;
9671     }
9672     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9673     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9674
9675     if (pn >= 2) {
9676         if (fenMode || line[0] == '#') pn--;
9677         while (pn > 0) {
9678             /* skip positions before number pn */
9679             if (fgets(line, MSG_SIZ, f) == NULL) {
9680                 Reset(TRUE, TRUE);
9681                 DisplayError(_("Position not found in file"), 0);
9682                 return FALSE;
9683             }
9684             if (fenMode || line[0] == '#') pn--;
9685         }
9686     }
9687
9688     if (fenMode) {
9689         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9690             DisplayError(_("Bad FEN position in file"), 0);
9691             return FALSE;
9692         }
9693     } else {
9694         (void) fgets(line, MSG_SIZ, f);
9695         (void) fgets(line, MSG_SIZ, f);
9696     
9697         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9698             (void) fgets(line, MSG_SIZ, f);
9699             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9700                 if (*p == ' ')
9701                   continue;
9702                 initial_position[i][j++] = CharToPiece(*p);
9703             }
9704         }
9705     
9706         blackPlaysFirst = FALSE;
9707         if (!feof(f)) {
9708             (void) fgets(line, MSG_SIZ, f);
9709             if (strncmp(line, "black", strlen("black"))==0)
9710               blackPlaysFirst = TRUE;
9711         }
9712     }
9713     startedFromSetupPosition = TRUE;
9714     
9715     SendToProgram("force\n", &first);
9716     CopyBoard(boards[0], initial_position);
9717     if (blackPlaysFirst) {
9718         currentMove = forwardMostMove = backwardMostMove = 1;
9719         strcpy(moveList[0], "");
9720         strcpy(parseList[0], "");
9721         CopyBoard(boards[1], initial_position);
9722         DisplayMessage("", _("Black to play"));
9723     } else {
9724         currentMove = forwardMostMove = backwardMostMove = 0;
9725         DisplayMessage("", _("White to play"));
9726     }
9727           /* [HGM] copy FEN attributes as well */
9728           {   int i;
9729               initialRulePlies = FENrulePlies;
9730               epStatus[forwardMostMove] = FENepStatus;
9731               for( i=0; i< nrCastlingRights; i++ )
9732                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9733           }
9734     SendBoard(&first, forwardMostMove);
9735     if (appData.debugMode) {
9736 int i, j;
9737   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9738   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9739         fprintf(debugFP, "Load Position\n");
9740     }
9741
9742     if (positionNumber > 1) {
9743         sprintf(line, "%s %d", title, positionNumber);
9744         DisplayTitle(line);
9745     } else {
9746         DisplayTitle(title);
9747     }
9748     gameMode = EditGame;
9749     ModeHighlight();
9750     ResetClocks();
9751     timeRemaining[0][1] = whiteTimeRemaining;
9752     timeRemaining[1][1] = blackTimeRemaining;
9753     DrawPosition(FALSE, boards[currentMove]);
9754    
9755     return TRUE;
9756 }
9757
9758
9759 void
9760 CopyPlayerNameIntoFileName(dest, src)
9761      char **dest, *src;
9762 {
9763     while (*src != NULLCHAR && *src != ',') {
9764         if (*src == ' ') {
9765             *(*dest)++ = '_';
9766             src++;
9767         } else {
9768             *(*dest)++ = *src++;
9769         }
9770     }
9771 }
9772
9773 char *DefaultFileName(ext)
9774      char *ext;
9775 {
9776     static char def[MSG_SIZ];
9777     char *p;
9778
9779     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9780         p = def;
9781         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9782         *p++ = '-';
9783         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9784         *p++ = '.';
9785         strcpy(p, ext);
9786     } else {
9787         def[0] = NULLCHAR;
9788     }
9789     return def;
9790 }
9791
9792 /* Save the current game to the given file */
9793 int
9794 SaveGameToFile(filename, append)
9795      char *filename;
9796      int append;
9797 {
9798     FILE *f;
9799     char buf[MSG_SIZ];
9800
9801     if (strcmp(filename, "-") == 0) {
9802         return SaveGame(stdout, 0, NULL);
9803     } else {
9804         f = fopen(filename, append ? "a" : "w");
9805         if (f == NULL) {
9806             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9807             DisplayError(buf, errno);
9808             return FALSE;
9809         } else {
9810             return SaveGame(f, 0, NULL);
9811         }
9812     }
9813 }
9814
9815 char *
9816 SavePart(str)
9817      char *str;
9818 {
9819     static char buf[MSG_SIZ];
9820     char *p;
9821     
9822     p = strchr(str, ' ');
9823     if (p == NULL) return str;
9824     strncpy(buf, str, p - str);
9825     buf[p - str] = NULLCHAR;
9826     return buf;
9827 }
9828
9829 #define PGN_MAX_LINE 75
9830
9831 #define PGN_SIDE_WHITE  0
9832 #define PGN_SIDE_BLACK  1
9833
9834 /* [AS] */
9835 static int FindFirstMoveOutOfBook( int side )
9836 {
9837     int result = -1;
9838
9839     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9840         int index = backwardMostMove;
9841         int has_book_hit = 0;
9842
9843         if( (index % 2) != side ) {
9844             index++;
9845         }
9846
9847         while( index < forwardMostMove ) {
9848             /* Check to see if engine is in book */
9849             int depth = pvInfoList[index].depth;
9850             int score = pvInfoList[index].score;
9851             int in_book = 0;
9852
9853             if( depth <= 2 ) {
9854                 in_book = 1;
9855             }
9856             else if( score == 0 && depth == 63 ) {
9857                 in_book = 1; /* Zappa */
9858             }
9859             else if( score == 2 && depth == 99 ) {
9860                 in_book = 1; /* Abrok */
9861             }
9862
9863             has_book_hit += in_book;
9864
9865             if( ! in_book ) {
9866                 result = index;
9867
9868                 break;
9869             }
9870
9871             index += 2;
9872         }
9873     }
9874
9875     return result;
9876 }
9877
9878 /* [AS] */
9879 void GetOutOfBookInfo( char * buf )
9880 {
9881     int oob[2];
9882     int i;
9883     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9884
9885     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9886     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9887
9888     *buf = '\0';
9889
9890     if( oob[0] >= 0 || oob[1] >= 0 ) {
9891         for( i=0; i<2; i++ ) {
9892             int idx = oob[i];
9893
9894             if( idx >= 0 ) {
9895                 if( i > 0 && oob[0] >= 0 ) {
9896                     strcat( buf, "   " );
9897                 }
9898
9899                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9900                 sprintf( buf+strlen(buf), "%s%.2f", 
9901                     pvInfoList[idx].score >= 0 ? "+" : "",
9902                     pvInfoList[idx].score / 100.0 );
9903             }
9904         }
9905     }
9906 }
9907
9908 /* Save game in PGN style and close the file */
9909 int
9910 SaveGamePGN(f)
9911      FILE *f;
9912 {
9913     int i, offset, linelen, newblock;
9914     time_t tm;
9915 //    char *movetext;
9916     char numtext[32];
9917     int movelen, numlen, blank;
9918     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9919
9920     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9921     
9922     tm = time((time_t *) NULL);
9923     
9924     PrintPGNTags(f, &gameInfo);
9925     
9926     if (backwardMostMove > 0 || startedFromSetupPosition) {
9927         char *fen = PositionToFEN(backwardMostMove, NULL);
9928         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9929         fprintf(f, "\n{--------------\n");
9930         PrintPosition(f, backwardMostMove);
9931         fprintf(f, "--------------}\n");
9932         free(fen);
9933     }
9934     else {
9935         /* [AS] Out of book annotation */
9936         if( appData.saveOutOfBookInfo ) {
9937             char buf[64];
9938
9939             GetOutOfBookInfo( buf );
9940
9941             if( buf[0] != '\0' ) {
9942                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9943             }
9944         }
9945
9946         fprintf(f, "\n");
9947     }
9948
9949     i = backwardMostMove;
9950     linelen = 0;
9951     newblock = TRUE;
9952
9953     while (i < forwardMostMove) {
9954         /* Print comments preceding this move */
9955         if (commentList[i] != NULL) {
9956             if (linelen > 0) fprintf(f, "\n");
9957             fprintf(f, "{\n%s}\n", commentList[i]);
9958             linelen = 0;
9959             newblock = TRUE;
9960         }
9961
9962         /* Format move number */
9963         if ((i % 2) == 0) {
9964             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9965         } else {
9966             if (newblock) {
9967                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9968             } else {
9969                 numtext[0] = NULLCHAR;
9970             }
9971         }
9972         numlen = strlen(numtext);
9973         newblock = FALSE;
9974
9975         /* Print move number */
9976         blank = linelen > 0 && numlen > 0;
9977         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9978             fprintf(f, "\n");
9979             linelen = 0;
9980             blank = 0;
9981         }
9982         if (blank) {
9983             fprintf(f, " ");
9984             linelen++;
9985         }
9986         fprintf(f, "%s", numtext);
9987         linelen += numlen;
9988
9989         /* Get move */
9990         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9991         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9992
9993         /* Print move */
9994         blank = linelen > 0 && movelen > 0;
9995         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9996             fprintf(f, "\n");
9997             linelen = 0;
9998             blank = 0;
9999         }
10000         if (blank) {
10001             fprintf(f, " ");
10002             linelen++;
10003         }
10004         fprintf(f, "%s", move_buffer);
10005         linelen += movelen;
10006
10007         /* [AS] Add PV info if present */
10008         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10009             /* [HGM] add time */
10010             char buf[MSG_SIZ]; int seconds;
10011
10012             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10013
10014             if( seconds <= 0) buf[0] = 0; else
10015             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10016                 seconds = (seconds + 4)/10; // round to full seconds
10017                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10018                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10019             }
10020
10021             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10022                 pvInfoList[i].score >= 0 ? "+" : "",
10023                 pvInfoList[i].score / 100.0,
10024                 pvInfoList[i].depth,
10025                 buf );
10026
10027             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10028
10029             /* Print score/depth */
10030             blank = linelen > 0 && movelen > 0;
10031             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10032                 fprintf(f, "\n");
10033                 linelen = 0;
10034                 blank = 0;
10035             }
10036             if (blank) {
10037                 fprintf(f, " ");
10038                 linelen++;
10039             }
10040             fprintf(f, "%s", move_buffer);
10041             linelen += movelen;
10042         }
10043
10044         i++;
10045     }
10046     
10047     /* Start a new line */
10048     if (linelen > 0) fprintf(f, "\n");
10049
10050     /* Print comments after last move */
10051     if (commentList[i] != NULL) {
10052         fprintf(f, "{\n%s}\n", commentList[i]);
10053     }
10054
10055     /* Print result */
10056     if (gameInfo.resultDetails != NULL &&
10057         gameInfo.resultDetails[0] != NULLCHAR) {
10058         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10059                 PGNResult(gameInfo.result));
10060     } else {
10061         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10062     }
10063
10064     fclose(f);
10065     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10066     return TRUE;
10067 }
10068
10069 /* Save game in old style and close the file */
10070 int
10071 SaveGameOldStyle(f)
10072      FILE *f;
10073 {
10074     int i, offset;
10075     time_t tm;
10076     
10077     tm = time((time_t *) NULL);
10078     
10079     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10080     PrintOpponents(f);
10081     
10082     if (backwardMostMove > 0 || startedFromSetupPosition) {
10083         fprintf(f, "\n[--------------\n");
10084         PrintPosition(f, backwardMostMove);
10085         fprintf(f, "--------------]\n");
10086     } else {
10087         fprintf(f, "\n");
10088     }
10089
10090     i = backwardMostMove;
10091     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10092
10093     while (i < forwardMostMove) {
10094         if (commentList[i] != NULL) {
10095             fprintf(f, "[%s]\n", commentList[i]);
10096         }
10097
10098         if ((i % 2) == 1) {
10099             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10100             i++;
10101         } else {
10102             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10103             i++;
10104             if (commentList[i] != NULL) {
10105                 fprintf(f, "\n");
10106                 continue;
10107             }
10108             if (i >= forwardMostMove) {
10109                 fprintf(f, "\n");
10110                 break;
10111             }
10112             fprintf(f, "%s\n", parseList[i]);
10113             i++;
10114         }
10115     }
10116     
10117     if (commentList[i] != NULL) {
10118         fprintf(f, "[%s]\n", commentList[i]);
10119     }
10120
10121     /* This isn't really the old style, but it's close enough */
10122     if (gameInfo.resultDetails != NULL &&
10123         gameInfo.resultDetails[0] != NULLCHAR) {
10124         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10125                 gameInfo.resultDetails);
10126     } else {
10127         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10128     }
10129
10130     fclose(f);
10131     return TRUE;
10132 }
10133
10134 /* Save the current game to open file f and close the file */
10135 int
10136 SaveGame(f, dummy, dummy2)
10137      FILE *f;
10138      int dummy;
10139      char *dummy2;
10140 {
10141     if (gameMode == EditPosition) EditPositionDone(TRUE);
10142     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10143     if (appData.oldSaveStyle)
10144       return SaveGameOldStyle(f);
10145     else
10146       return SaveGamePGN(f);
10147 }
10148
10149 /* Save the current position to the given file */
10150 int
10151 SavePositionToFile(filename)
10152      char *filename;
10153 {
10154     FILE *f;
10155     char buf[MSG_SIZ];
10156
10157     if (strcmp(filename, "-") == 0) {
10158         return SavePosition(stdout, 0, NULL);
10159     } else {
10160         f = fopen(filename, "a");
10161         if (f == NULL) {
10162             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10163             DisplayError(buf, errno);
10164             return FALSE;
10165         } else {
10166             SavePosition(f, 0, NULL);
10167             return TRUE;
10168         }
10169     }
10170 }
10171
10172 /* Save the current position to the given open file and close the file */
10173 int
10174 SavePosition(f, dummy, dummy2)
10175      FILE *f;
10176      int dummy;
10177      char *dummy2;
10178 {
10179     time_t tm;
10180     char *fen;
10181
10182     if (gameMode == EditPosition) EditPositionDone(TRUE);
10183     if (appData.oldSaveStyle) {
10184         tm = time((time_t *) NULL);
10185     
10186         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10187         PrintOpponents(f);
10188         fprintf(f, "[--------------\n");
10189         PrintPosition(f, currentMove);
10190         fprintf(f, "--------------]\n");
10191     } else {
10192         fen = PositionToFEN(currentMove, NULL);
10193         fprintf(f, "%s\n", fen);
10194         free(fen);
10195     }
10196     fclose(f);
10197     return TRUE;
10198 }
10199
10200 void
10201 ReloadCmailMsgEvent(unregister)
10202      int unregister;
10203 {
10204 #if !WIN32
10205     static char *inFilename = NULL;
10206     static char *outFilename;
10207     int i;
10208     struct stat inbuf, outbuf;
10209     int status;
10210     
10211     /* Any registered moves are unregistered if unregister is set, */
10212     /* i.e. invoked by the signal handler */
10213     if (unregister) {
10214         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10215             cmailMoveRegistered[i] = FALSE;
10216             if (cmailCommentList[i] != NULL) {
10217                 free(cmailCommentList[i]);
10218                 cmailCommentList[i] = NULL;
10219             }
10220         }
10221         nCmailMovesRegistered = 0;
10222     }
10223
10224     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10225         cmailResult[i] = CMAIL_NOT_RESULT;
10226     }
10227     nCmailResults = 0;
10228
10229     if (inFilename == NULL) {
10230         /* Because the filenames are static they only get malloced once  */
10231         /* and they never get freed                                      */
10232         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10233         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10234
10235         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10236         sprintf(outFilename, "%s.out", appData.cmailGameName);
10237     }
10238     
10239     status = stat(outFilename, &outbuf);
10240     if (status < 0) {
10241         cmailMailedMove = FALSE;
10242     } else {
10243         status = stat(inFilename, &inbuf);
10244         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10245     }
10246     
10247     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10248        counts the games, notes how each one terminated, etc.
10249        
10250        It would be nice to remove this kludge and instead gather all
10251        the information while building the game list.  (And to keep it
10252        in the game list nodes instead of having a bunch of fixed-size
10253        parallel arrays.)  Note this will require getting each game's
10254        termination from the PGN tags, as the game list builder does
10255        not process the game moves.  --mann
10256        */
10257     cmailMsgLoaded = TRUE;
10258     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10259     
10260     /* Load first game in the file or popup game menu */
10261     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10262
10263 #endif /* !WIN32 */
10264     return;
10265 }
10266
10267 int
10268 RegisterMove()
10269 {
10270     FILE *f;
10271     char string[MSG_SIZ];
10272
10273     if (   cmailMailedMove
10274         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10275         return TRUE;            /* Allow free viewing  */
10276     }
10277
10278     /* Unregister move to ensure that we don't leave RegisterMove        */
10279     /* with the move registered when the conditions for registering no   */
10280     /* longer hold                                                       */
10281     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10282         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10283         nCmailMovesRegistered --;
10284
10285         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10286           {
10287               free(cmailCommentList[lastLoadGameNumber - 1]);
10288               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10289           }
10290     }
10291
10292     if (cmailOldMove == -1) {
10293         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10294         return FALSE;
10295     }
10296
10297     if (currentMove > cmailOldMove + 1) {
10298         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10299         return FALSE;
10300     }
10301
10302     if (currentMove < cmailOldMove) {
10303         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10304         return FALSE;
10305     }
10306
10307     if (forwardMostMove > currentMove) {
10308         /* Silently truncate extra moves */
10309         TruncateGame();
10310     }
10311
10312     if (   (currentMove == cmailOldMove + 1)
10313         || (   (currentMove == cmailOldMove)
10314             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10315                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10316         if (gameInfo.result != GameUnfinished) {
10317             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10318         }
10319
10320         if (commentList[currentMove] != NULL) {
10321             cmailCommentList[lastLoadGameNumber - 1]
10322               = StrSave(commentList[currentMove]);
10323         }
10324         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10325
10326         if (appData.debugMode)
10327           fprintf(debugFP, "Saving %s for game %d\n",
10328                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10329
10330         sprintf(string,
10331                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10332         
10333         f = fopen(string, "w");
10334         if (appData.oldSaveStyle) {
10335             SaveGameOldStyle(f); /* also closes the file */
10336             
10337             sprintf(string, "%s.pos.out", appData.cmailGameName);
10338             f = fopen(string, "w");
10339             SavePosition(f, 0, NULL); /* also closes the file */
10340         } else {
10341             fprintf(f, "{--------------\n");
10342             PrintPosition(f, currentMove);
10343             fprintf(f, "--------------}\n\n");
10344             
10345             SaveGame(f, 0, NULL); /* also closes the file*/
10346         }
10347         
10348         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10349         nCmailMovesRegistered ++;
10350     } else if (nCmailGames == 1) {
10351         DisplayError(_("You have not made a move yet"), 0);
10352         return FALSE;
10353     }
10354
10355     return TRUE;
10356 }
10357
10358 void
10359 MailMoveEvent()
10360 {
10361 #if !WIN32
10362     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10363     FILE *commandOutput;
10364     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10365     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10366     int nBuffers;
10367     int i;
10368     int archived;
10369     char *arcDir;
10370
10371     if (! cmailMsgLoaded) {
10372         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10373         return;
10374     }
10375
10376     if (nCmailGames == nCmailResults) {
10377         DisplayError(_("No unfinished games"), 0);
10378         return;
10379     }
10380
10381 #if CMAIL_PROHIBIT_REMAIL
10382     if (cmailMailedMove) {
10383         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);
10384         DisplayError(msg, 0);
10385         return;
10386     }
10387 #endif
10388
10389     if (! (cmailMailedMove || RegisterMove())) return;
10390     
10391     if (   cmailMailedMove
10392         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10393         sprintf(string, partCommandString,
10394                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10395         commandOutput = popen(string, "r");
10396
10397         if (commandOutput == NULL) {
10398             DisplayError(_("Failed to invoke cmail"), 0);
10399         } else {
10400             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10401                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10402             }
10403             if (nBuffers > 1) {
10404                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10405                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10406                 nBytes = MSG_SIZ - 1;
10407             } else {
10408                 (void) memcpy(msg, buffer, nBytes);
10409             }
10410             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10411
10412             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10413                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10414
10415                 archived = TRUE;
10416                 for (i = 0; i < nCmailGames; i ++) {
10417                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10418                         archived = FALSE;
10419                     }
10420                 }
10421                 if (   archived
10422                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10423                         != NULL)) {
10424                     sprintf(buffer, "%s/%s.%s.archive",
10425                             arcDir,
10426                             appData.cmailGameName,
10427                             gameInfo.date);
10428                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10429                     cmailMsgLoaded = FALSE;
10430                 }
10431             }
10432
10433             DisplayInformation(msg);
10434             pclose(commandOutput);
10435         }
10436     } else {
10437         if ((*cmailMsg) != '\0') {
10438             DisplayInformation(cmailMsg);
10439         }
10440     }
10441
10442     return;
10443 #endif /* !WIN32 */
10444 }
10445
10446 char *
10447 CmailMsg()
10448 {
10449 #if WIN32
10450     return NULL;
10451 #else
10452     int  prependComma = 0;
10453     char number[5];
10454     char string[MSG_SIZ];       /* Space for game-list */
10455     int  i;
10456     
10457     if (!cmailMsgLoaded) return "";
10458
10459     if (cmailMailedMove) {
10460         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10461     } else {
10462         /* Create a list of games left */
10463         sprintf(string, "[");
10464         for (i = 0; i < nCmailGames; i ++) {
10465             if (! (   cmailMoveRegistered[i]
10466                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10467                 if (prependComma) {
10468                     sprintf(number, ",%d", i + 1);
10469                 } else {
10470                     sprintf(number, "%d", i + 1);
10471                     prependComma = 1;
10472                 }
10473                 
10474                 strcat(string, number);
10475             }
10476         }
10477         strcat(string, "]");
10478
10479         if (nCmailMovesRegistered + nCmailResults == 0) {
10480             switch (nCmailGames) {
10481               case 1:
10482                 sprintf(cmailMsg,
10483                         _("Still need to make move for game\n"));
10484                 break;
10485                 
10486               case 2:
10487                 sprintf(cmailMsg,
10488                         _("Still need to make moves for both games\n"));
10489                 break;
10490                 
10491               default:
10492                 sprintf(cmailMsg,
10493                         _("Still need to make moves for all %d games\n"),
10494                         nCmailGames);
10495                 break;
10496             }
10497         } else {
10498             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10499               case 1:
10500                 sprintf(cmailMsg,
10501                         _("Still need to make a move for game %s\n"),
10502                         string);
10503                 break;
10504                 
10505               case 0:
10506                 if (nCmailResults == nCmailGames) {
10507                     sprintf(cmailMsg, _("No unfinished games\n"));
10508                 } else {
10509                     sprintf(cmailMsg, _("Ready to send mail\n"));
10510                 }
10511                 break;
10512                 
10513               default:
10514                 sprintf(cmailMsg,
10515                         _("Still need to make moves for games %s\n"),
10516                         string);
10517             }
10518         }
10519     }
10520     return cmailMsg;
10521 #endif /* WIN32 */
10522 }
10523
10524 void
10525 ResetGameEvent()
10526 {
10527     if (gameMode == Training)
10528       SetTrainingModeOff();
10529
10530     Reset(TRUE, TRUE);
10531     cmailMsgLoaded = FALSE;
10532     if (appData.icsActive) {
10533       SendToICS(ics_prefix);
10534       SendToICS("refresh\n");
10535     }
10536 }
10537
10538 void
10539 ExitEvent(status)
10540      int status;
10541 {
10542     exiting++;
10543     if (exiting > 2) {
10544       /* Give up on clean exit */
10545       exit(status);
10546     }
10547     if (exiting > 1) {
10548       /* Keep trying for clean exit */
10549       return;
10550     }
10551
10552     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10553
10554     if (telnetISR != NULL) {
10555       RemoveInputSource(telnetISR);
10556     }
10557     if (icsPR != NoProc) {
10558       DestroyChildProcess(icsPR, TRUE);
10559     }
10560
10561     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10562     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10563
10564     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10565     /* make sure this other one finishes before killing it!                  */
10566     if(endingGame) { int count = 0;
10567         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10568         while(endingGame && count++ < 10) DoSleep(1);
10569         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10570     }
10571
10572     /* Kill off chess programs */
10573     if (first.pr != NoProc) {
10574         ExitAnalyzeMode();
10575         
10576         DoSleep( appData.delayBeforeQuit );
10577         SendToProgram("quit\n", &first);
10578         DoSleep( appData.delayAfterQuit );
10579         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10580     }
10581     if (second.pr != NoProc) {
10582         DoSleep( appData.delayBeforeQuit );
10583         SendToProgram("quit\n", &second);
10584         DoSleep( appData.delayAfterQuit );
10585         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10586     }
10587     if (first.isr != NULL) {
10588         RemoveInputSource(first.isr);
10589     }
10590     if (second.isr != NULL) {
10591         RemoveInputSource(second.isr);
10592     }
10593
10594     ShutDownFrontEnd();
10595     exit(status);
10596 }
10597
10598 void
10599 PauseEvent()
10600 {
10601     if (appData.debugMode)
10602         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10603     if (pausing) {
10604         pausing = FALSE;
10605         ModeHighlight();
10606         if (gameMode == MachinePlaysWhite ||
10607             gameMode == MachinePlaysBlack) {
10608             StartClocks();
10609         } else {
10610             DisplayBothClocks();
10611         }
10612         if (gameMode == PlayFromGameFile) {
10613             if (appData.timeDelay >= 0) 
10614                 AutoPlayGameLoop();
10615         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10616             Reset(FALSE, TRUE);
10617             SendToICS(ics_prefix);
10618             SendToICS("refresh\n");
10619         } else if (currentMove < forwardMostMove) {
10620             ForwardInner(forwardMostMove);
10621         }
10622         pauseExamInvalid = FALSE;
10623     } else {
10624         switch (gameMode) {
10625           default:
10626             return;
10627           case IcsExamining:
10628             pauseExamForwardMostMove = forwardMostMove;
10629             pauseExamInvalid = FALSE;
10630             /* fall through */
10631           case IcsObserving:
10632           case IcsPlayingWhite:
10633           case IcsPlayingBlack:
10634             pausing = TRUE;
10635             ModeHighlight();
10636             return;
10637           case PlayFromGameFile:
10638             (void) StopLoadGameTimer();
10639             pausing = TRUE;
10640             ModeHighlight();
10641             break;
10642           case BeginningOfGame:
10643             if (appData.icsActive) return;
10644             /* else fall through */
10645           case MachinePlaysWhite:
10646           case MachinePlaysBlack:
10647           case TwoMachinesPlay:
10648             if (forwardMostMove == 0)
10649               return;           /* don't pause if no one has moved */
10650             if ((gameMode == MachinePlaysWhite &&
10651                  !WhiteOnMove(forwardMostMove)) ||
10652                 (gameMode == MachinePlaysBlack &&
10653                  WhiteOnMove(forwardMostMove))) {
10654                 StopClocks();
10655             }
10656             pausing = TRUE;
10657             ModeHighlight();
10658             break;
10659         }
10660     }
10661 }
10662
10663 void
10664 EditCommentEvent()
10665 {
10666     char title[MSG_SIZ];
10667
10668     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10669         strcpy(title, _("Edit comment"));
10670     } else {
10671         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10672                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10673                 parseList[currentMove - 1]);
10674     }
10675
10676     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10677 }
10678
10679
10680 void
10681 EditTagsEvent()
10682 {
10683     char *tags = PGNTags(&gameInfo);
10684     EditTagsPopUp(tags);
10685     free(tags);
10686 }
10687
10688 void
10689 AnalyzeModeEvent()
10690 {
10691     if (appData.noChessProgram || gameMode == AnalyzeMode)
10692       return;
10693
10694     if (gameMode != AnalyzeFile) {
10695         if (!appData.icsEngineAnalyze) {
10696                EditGameEvent();
10697                if (gameMode != EditGame) return;
10698         }
10699         ResurrectChessProgram();
10700         SendToProgram("analyze\n", &first);
10701         first.analyzing = TRUE;
10702         /*first.maybeThinking = TRUE;*/
10703         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10704         EngineOutputPopUp();
10705     }
10706     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10707     pausing = FALSE;
10708     ModeHighlight();
10709     SetGameInfo();
10710
10711     StartAnalysisClock();
10712     GetTimeMark(&lastNodeCountTime);
10713     lastNodeCount = 0;
10714 }
10715
10716 void
10717 AnalyzeFileEvent()
10718 {
10719     if (appData.noChessProgram || gameMode == AnalyzeFile)
10720       return;
10721
10722     if (gameMode != AnalyzeMode) {
10723         EditGameEvent();
10724         if (gameMode != EditGame) return;
10725         ResurrectChessProgram();
10726         SendToProgram("analyze\n", &first);
10727         first.analyzing = TRUE;
10728         /*first.maybeThinking = TRUE;*/
10729         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10730         EngineOutputPopUp();
10731     }
10732     gameMode = AnalyzeFile;
10733     pausing = FALSE;
10734     ModeHighlight();
10735     SetGameInfo();
10736
10737     StartAnalysisClock();
10738     GetTimeMark(&lastNodeCountTime);
10739     lastNodeCount = 0;
10740 }
10741
10742 void
10743 MachineWhiteEvent()
10744 {
10745     char buf[MSG_SIZ];
10746     char *bookHit = NULL;
10747
10748     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10749       return;
10750
10751
10752     if (gameMode == PlayFromGameFile || 
10753         gameMode == TwoMachinesPlay  || 
10754         gameMode == Training         || 
10755         gameMode == AnalyzeMode      || 
10756         gameMode == EndOfGame)
10757         EditGameEvent();
10758
10759     if (gameMode == EditPosition) 
10760         EditPositionDone(TRUE);
10761
10762     if (!WhiteOnMove(currentMove)) {
10763         DisplayError(_("It is not White's turn"), 0);
10764         return;
10765     }
10766   
10767     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10768       ExitAnalyzeMode();
10769
10770     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10771         gameMode == AnalyzeFile)
10772         TruncateGame();
10773
10774     ResurrectChessProgram();    /* in case it isn't running */
10775     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10776         gameMode = MachinePlaysWhite;
10777         ResetClocks();
10778     } else
10779     gameMode = MachinePlaysWhite;
10780     pausing = FALSE;
10781     ModeHighlight();
10782     SetGameInfo();
10783     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10784     DisplayTitle(buf);
10785     if (first.sendName) {
10786       sprintf(buf, "name %s\n", gameInfo.black);
10787       SendToProgram(buf, &first);
10788     }
10789     if (first.sendTime) {
10790       if (first.useColors) {
10791         SendToProgram("black\n", &first); /*gnu kludge*/
10792       }
10793       SendTimeRemaining(&first, TRUE);
10794     }
10795     if (first.useColors) {
10796       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10797     }
10798     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10799     SetMachineThinkingEnables();
10800     first.maybeThinking = TRUE;
10801     StartClocks();
10802     firstMove = FALSE;
10803
10804     if (appData.autoFlipView && !flipView) {
10805       flipView = !flipView;
10806       DrawPosition(FALSE, NULL);
10807       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10808     }
10809
10810     if(bookHit) { // [HGM] book: simulate book reply
10811         static char bookMove[MSG_SIZ]; // a bit generous?
10812
10813         programStats.nodes = programStats.depth = programStats.time = 
10814         programStats.score = programStats.got_only_move = 0;
10815         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10816
10817         strcpy(bookMove, "move ");
10818         strcat(bookMove, bookHit);
10819         HandleMachineMove(bookMove, &first);
10820     }
10821 }
10822
10823 void
10824 MachineBlackEvent()
10825 {
10826     char buf[MSG_SIZ];
10827    char *bookHit = NULL;
10828
10829     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10830         return;
10831
10832
10833     if (gameMode == PlayFromGameFile || 
10834         gameMode == TwoMachinesPlay  || 
10835         gameMode == Training         || 
10836         gameMode == AnalyzeMode      || 
10837         gameMode == EndOfGame)
10838         EditGameEvent();
10839
10840     if (gameMode == EditPosition) 
10841         EditPositionDone(TRUE);
10842
10843     if (WhiteOnMove(currentMove)) {
10844         DisplayError(_("It is not Black's turn"), 0);
10845         return;
10846     }
10847     
10848     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10849       ExitAnalyzeMode();
10850
10851     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10852         gameMode == AnalyzeFile)
10853         TruncateGame();
10854
10855     ResurrectChessProgram();    /* in case it isn't running */
10856     gameMode = MachinePlaysBlack;
10857     pausing = FALSE;
10858     ModeHighlight();
10859     SetGameInfo();
10860     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10861     DisplayTitle(buf);
10862     if (first.sendName) {
10863       sprintf(buf, "name %s\n", gameInfo.white);
10864       SendToProgram(buf, &first);
10865     }
10866     if (first.sendTime) {
10867       if (first.useColors) {
10868         SendToProgram("white\n", &first); /*gnu kludge*/
10869       }
10870       SendTimeRemaining(&first, FALSE);
10871     }
10872     if (first.useColors) {
10873       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10874     }
10875     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10876     SetMachineThinkingEnables();
10877     first.maybeThinking = TRUE;
10878     StartClocks();
10879
10880     if (appData.autoFlipView && flipView) {
10881       flipView = !flipView;
10882       DrawPosition(FALSE, NULL);
10883       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10884     }
10885     if(bookHit) { // [HGM] book: simulate book reply
10886         static char bookMove[MSG_SIZ]; // a bit generous?
10887
10888         programStats.nodes = programStats.depth = programStats.time = 
10889         programStats.score = programStats.got_only_move = 0;
10890         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10891
10892         strcpy(bookMove, "move ");
10893         strcat(bookMove, bookHit);
10894         HandleMachineMove(bookMove, &first);
10895     }
10896 }
10897
10898
10899 void
10900 DisplayTwoMachinesTitle()
10901 {
10902     char buf[MSG_SIZ];
10903     if (appData.matchGames > 0) {
10904         if (first.twoMachinesColor[0] == 'w') {
10905             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10906                     gameInfo.white, gameInfo.black,
10907                     first.matchWins, second.matchWins,
10908                     matchGame - 1 - (first.matchWins + second.matchWins));
10909         } else {
10910             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10911                     gameInfo.white, gameInfo.black,
10912                     second.matchWins, first.matchWins,
10913                     matchGame - 1 - (first.matchWins + second.matchWins));
10914         }
10915     } else {
10916         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10917     }
10918     DisplayTitle(buf);
10919 }
10920
10921 void
10922 TwoMachinesEvent P((void))
10923 {
10924     int i;
10925     char buf[MSG_SIZ];
10926     ChessProgramState *onmove;
10927     char *bookHit = NULL;
10928     
10929     if (appData.noChessProgram) return;
10930
10931     switch (gameMode) {
10932       case TwoMachinesPlay:
10933         return;
10934       case MachinePlaysWhite:
10935       case MachinePlaysBlack:
10936         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10937             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10938             return;
10939         }
10940         /* fall through */
10941       case BeginningOfGame:
10942       case PlayFromGameFile:
10943       case EndOfGame:
10944         EditGameEvent();
10945         if (gameMode != EditGame) return;
10946         break;
10947       case EditPosition:
10948         EditPositionDone(TRUE);
10949         break;
10950       case AnalyzeMode:
10951       case AnalyzeFile:
10952         ExitAnalyzeMode();
10953         break;
10954       case EditGame:
10955       default:
10956         break;
10957     }
10958
10959     forwardMostMove = currentMove;
10960     ResurrectChessProgram();    /* in case first program isn't running */
10961
10962     if (second.pr == NULL) {
10963         StartChessProgram(&second);
10964         if (second.protocolVersion == 1) {
10965           TwoMachinesEventIfReady();
10966         } else {
10967           /* kludge: allow timeout for initial "feature" command */
10968           FreezeUI();
10969           DisplayMessage("", _("Starting second chess program"));
10970           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10971         }
10972         return;
10973     }
10974     DisplayMessage("", "");
10975     InitChessProgram(&second, FALSE);
10976     SendToProgram("force\n", &second);
10977     if (startedFromSetupPosition) {
10978         SendBoard(&second, backwardMostMove);
10979     if (appData.debugMode) {
10980         fprintf(debugFP, "Two Machines\n");
10981     }
10982     }
10983     for (i = backwardMostMove; i < forwardMostMove; i++) {
10984         SendMoveToProgram(i, &second);
10985     }
10986
10987     gameMode = TwoMachinesPlay;
10988     pausing = FALSE;
10989     ModeHighlight();
10990     SetGameInfo();
10991     DisplayTwoMachinesTitle();
10992     firstMove = TRUE;
10993     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10994         onmove = &first;
10995     } else {
10996         onmove = &second;
10997     }
10998
10999     SendToProgram(first.computerString, &first);
11000     if (first.sendName) {
11001       sprintf(buf, "name %s\n", second.tidy);
11002       SendToProgram(buf, &first);
11003     }
11004     SendToProgram(second.computerString, &second);
11005     if (second.sendName) {
11006       sprintf(buf, "name %s\n", first.tidy);
11007       SendToProgram(buf, &second);
11008     }
11009
11010     ResetClocks();
11011     if (!first.sendTime || !second.sendTime) {
11012         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11013         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11014     }
11015     if (onmove->sendTime) {
11016       if (onmove->useColors) {
11017         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11018       }
11019       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11020     }
11021     if (onmove->useColors) {
11022       SendToProgram(onmove->twoMachinesColor, onmove);
11023     }
11024     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11025 //    SendToProgram("go\n", onmove);
11026     onmove->maybeThinking = TRUE;
11027     SetMachineThinkingEnables();
11028
11029     StartClocks();
11030
11031     if(bookHit) { // [HGM] book: simulate book reply
11032         static char bookMove[MSG_SIZ]; // a bit generous?
11033
11034         programStats.nodes = programStats.depth = programStats.time = 
11035         programStats.score = programStats.got_only_move = 0;
11036         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11037
11038         strcpy(bookMove, "move ");
11039         strcat(bookMove, bookHit);
11040         savedMessage = bookMove; // args for deferred call
11041         savedState = onmove;
11042         ScheduleDelayedEvent(DeferredBookMove, 1);
11043     }
11044 }
11045
11046 void
11047 TrainingEvent()
11048 {
11049     if (gameMode == Training) {
11050       SetTrainingModeOff();
11051       gameMode = PlayFromGameFile;
11052       DisplayMessage("", _("Training mode off"));
11053     } else {
11054       gameMode = Training;
11055       animateTraining = appData.animate;
11056
11057       /* make sure we are not already at the end of the game */
11058       if (currentMove < forwardMostMove) {
11059         SetTrainingModeOn();
11060         DisplayMessage("", _("Training mode on"));
11061       } else {
11062         gameMode = PlayFromGameFile;
11063         DisplayError(_("Already at end of game"), 0);
11064       }
11065     }
11066     ModeHighlight();
11067 }
11068
11069 void
11070 IcsClientEvent()
11071 {
11072     if (!appData.icsActive) return;
11073     switch (gameMode) {
11074       case IcsPlayingWhite:
11075       case IcsPlayingBlack:
11076       case IcsObserving:
11077       case IcsIdle:
11078       case BeginningOfGame:
11079       case IcsExamining:
11080         return;
11081
11082       case EditGame:
11083         break;
11084
11085       case EditPosition:
11086         EditPositionDone(TRUE);
11087         break;
11088
11089       case AnalyzeMode:
11090       case AnalyzeFile:
11091         ExitAnalyzeMode();
11092         break;
11093         
11094       default:
11095         EditGameEvent();
11096         break;
11097     }
11098
11099     gameMode = IcsIdle;
11100     ModeHighlight();
11101     return;
11102 }
11103
11104
11105 void
11106 EditGameEvent()
11107 {
11108     int i;
11109
11110     switch (gameMode) {
11111       case Training:
11112         SetTrainingModeOff();
11113         break;
11114       case MachinePlaysWhite:
11115       case MachinePlaysBlack:
11116       case BeginningOfGame:
11117         SendToProgram("force\n", &first);
11118         SetUserThinkingEnables();
11119         break;
11120       case PlayFromGameFile:
11121         (void) StopLoadGameTimer();
11122         if (gameFileFP != NULL) {
11123             gameFileFP = NULL;
11124         }
11125         break;
11126       case EditPosition:
11127         EditPositionDone(TRUE);
11128         break;
11129       case AnalyzeMode:
11130       case AnalyzeFile:
11131         ExitAnalyzeMode();
11132         SendToProgram("force\n", &first);
11133         break;
11134       case TwoMachinesPlay:
11135         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11136         ResurrectChessProgram();
11137         SetUserThinkingEnables();
11138         break;
11139       case EndOfGame:
11140         ResurrectChessProgram();
11141         break;
11142       case IcsPlayingBlack:
11143       case IcsPlayingWhite:
11144         DisplayError(_("Warning: You are still playing a game"), 0);
11145         break;
11146       case IcsObserving:
11147         DisplayError(_("Warning: You are still observing a game"), 0);
11148         break;
11149       case IcsExamining:
11150         DisplayError(_("Warning: You are still examining a game"), 0);
11151         break;
11152       case IcsIdle:
11153         break;
11154       case EditGame:
11155       default:
11156         return;
11157     }
11158     
11159     pausing = FALSE;
11160     StopClocks();
11161     first.offeredDraw = second.offeredDraw = 0;
11162
11163     if (gameMode == PlayFromGameFile) {
11164         whiteTimeRemaining = timeRemaining[0][currentMove];
11165         blackTimeRemaining = timeRemaining[1][currentMove];
11166         DisplayTitle("");
11167     }
11168
11169     if (gameMode == MachinePlaysWhite ||
11170         gameMode == MachinePlaysBlack ||
11171         gameMode == TwoMachinesPlay ||
11172         gameMode == EndOfGame) {
11173         i = forwardMostMove;
11174         while (i > currentMove) {
11175             SendToProgram("undo\n", &first);
11176             i--;
11177         }
11178         whiteTimeRemaining = timeRemaining[0][currentMove];
11179         blackTimeRemaining = timeRemaining[1][currentMove];
11180         DisplayBothClocks();
11181         if (whiteFlag || blackFlag) {
11182             whiteFlag = blackFlag = 0;
11183         }
11184         DisplayTitle("");
11185     }           
11186     
11187     gameMode = EditGame;
11188     ModeHighlight();
11189     SetGameInfo();
11190 }
11191
11192
11193 void
11194 EditPositionEvent()
11195 {
11196     if (gameMode == EditPosition) {
11197         EditGameEvent();
11198         return;
11199     }
11200     
11201     EditGameEvent();
11202     if (gameMode != EditGame) return;
11203     
11204     gameMode = EditPosition;
11205     ModeHighlight();
11206     SetGameInfo();
11207     if (currentMove > 0)
11208       CopyBoard(boards[0], boards[currentMove]);
11209     
11210     blackPlaysFirst = !WhiteOnMove(currentMove);
11211     ResetClocks();
11212     currentMove = forwardMostMove = backwardMostMove = 0;
11213     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11214     DisplayMove(-1);
11215 }
11216
11217 void
11218 ExitAnalyzeMode()
11219 {
11220     /* [DM] icsEngineAnalyze - possible call from other functions */
11221     if (appData.icsEngineAnalyze) {
11222         appData.icsEngineAnalyze = FALSE;
11223
11224         DisplayMessage("",_("Close ICS engine analyze..."));
11225     }
11226     if (first.analysisSupport && first.analyzing) {
11227       SendToProgram("exit\n", &first);
11228       first.analyzing = FALSE;
11229     }
11230     thinkOutput[0] = NULLCHAR;
11231 }
11232
11233 void
11234 EditPositionDone(Boolean fakeRights)
11235 {
11236     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11237
11238     startedFromSetupPosition = TRUE;
11239     InitChessProgram(&first, FALSE);
11240     if(fakeRights)  
11241       { /* don't do this if we just pasted FEN */
11242         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11243         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11244           {
11245             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11246             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11247           } 
11248         else 
11249           castlingRights[0][2] = -1;
11250         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11251           {
11252             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11253             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11254           } 
11255         else 
11256           castlingRights[0][5] = -1;
11257       }
11258     SendToProgram("force\n", &first);
11259     if (blackPlaysFirst) {
11260         strcpy(moveList[0], "");
11261         strcpy(parseList[0], "");
11262         currentMove = forwardMostMove = backwardMostMove = 1;
11263         CopyBoard(boards[1], boards[0]);
11264         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11265         { int i;
11266           epStatus[1] = epStatus[0];
11267           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11268         }
11269     } else {
11270         currentMove = forwardMostMove = backwardMostMove = 0;
11271     }
11272     SendBoard(&first, forwardMostMove);
11273     if (appData.debugMode) {
11274         fprintf(debugFP, "EditPosDone\n");
11275     }
11276     DisplayTitle("");
11277     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11278     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11279     gameMode = EditGame;
11280     ModeHighlight();
11281     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11282     ClearHighlights(); /* [AS] */
11283 }
11284
11285 /* Pause for `ms' milliseconds */
11286 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11287 void
11288 TimeDelay(ms)
11289      long ms;
11290 {
11291     TimeMark m1, m2;
11292
11293     GetTimeMark(&m1);
11294     do {
11295         GetTimeMark(&m2);
11296     } while (SubtractTimeMarks(&m2, &m1) < ms);
11297 }
11298
11299 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11300 void
11301 SendMultiLineToICS(buf)
11302      char *buf;
11303 {
11304     char temp[MSG_SIZ+1], *p;
11305     int len;
11306
11307     len = strlen(buf);
11308     if (len > MSG_SIZ)
11309       len = MSG_SIZ;
11310   
11311     strncpy(temp, buf, len);
11312     temp[len] = 0;
11313
11314     p = temp;
11315     while (*p) {
11316         if (*p == '\n' || *p == '\r')
11317           *p = ' ';
11318         ++p;
11319     }
11320
11321     strcat(temp, "\n");
11322     SendToICS(temp);
11323     SendToPlayer(temp, strlen(temp));
11324 }
11325
11326 void
11327 SetWhiteToPlayEvent()
11328 {
11329     if (gameMode == EditPosition) {
11330         blackPlaysFirst = FALSE;
11331         DisplayBothClocks();    /* works because currentMove is 0 */
11332     } else if (gameMode == IcsExamining) {
11333         SendToICS(ics_prefix);
11334         SendToICS("tomove white\n");
11335     }
11336 }
11337
11338 void
11339 SetBlackToPlayEvent()
11340 {
11341     if (gameMode == EditPosition) {
11342         blackPlaysFirst = TRUE;
11343         currentMove = 1;        /* kludge */
11344         DisplayBothClocks();
11345         currentMove = 0;
11346     } else if (gameMode == IcsExamining) {
11347         SendToICS(ics_prefix);
11348         SendToICS("tomove black\n");
11349     }
11350 }
11351
11352 void
11353 EditPositionMenuEvent(selection, x, y)
11354      ChessSquare selection;
11355      int x, y;
11356 {
11357     char buf[MSG_SIZ];
11358     ChessSquare piece = boards[0][y][x];
11359
11360     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11361
11362     switch (selection) {
11363       case ClearBoard:
11364         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11365             SendToICS(ics_prefix);
11366             SendToICS("bsetup clear\n");
11367         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11368             SendToICS(ics_prefix);
11369             SendToICS("clearboard\n");
11370         } else {
11371             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11372                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11373                 for (y = 0; y < BOARD_HEIGHT; y++) {
11374                     if (gameMode == IcsExamining) {
11375                         if (boards[currentMove][y][x] != EmptySquare) {
11376                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11377                                     AAA + x, ONE + y);
11378                             SendToICS(buf);
11379                         }
11380                     } else {
11381                         boards[0][y][x] = p;
11382                     }
11383                 }
11384             }
11385         }
11386         if (gameMode == EditPosition) {
11387             DrawPosition(FALSE, boards[0]);
11388         }
11389         break;
11390
11391       case WhitePlay:
11392         SetWhiteToPlayEvent();
11393         break;
11394
11395       case BlackPlay:
11396         SetBlackToPlayEvent();
11397         break;
11398
11399       case EmptySquare:
11400         if (gameMode == IcsExamining) {
11401             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11402             SendToICS(buf);
11403         } else {
11404             boards[0][y][x] = EmptySquare;
11405             DrawPosition(FALSE, boards[0]);
11406         }
11407         break;
11408
11409       case PromotePiece:
11410         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11411            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11412             selection = (ChessSquare) (PROMOTED piece);
11413         } else if(piece == EmptySquare) selection = WhiteSilver;
11414         else selection = (ChessSquare)((int)piece - 1);
11415         goto defaultlabel;
11416
11417       case DemotePiece:
11418         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11419            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11420             selection = (ChessSquare) (DEMOTED piece);
11421         } else if(piece == EmptySquare) selection = BlackSilver;
11422         else selection = (ChessSquare)((int)piece + 1);       
11423         goto defaultlabel;
11424
11425       case WhiteQueen:
11426       case BlackQueen:
11427         if(gameInfo.variant == VariantShatranj ||
11428            gameInfo.variant == VariantXiangqi  ||
11429            gameInfo.variant == VariantCourier  ||
11430            gameInfo.variant == VariantMakruk     )
11431             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11432         goto defaultlabel;
11433
11434       case WhiteKing:
11435       case BlackKing:
11436         if(gameInfo.variant == VariantXiangqi)
11437             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11438         if(gameInfo.variant == VariantKnightmate)
11439             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11440       default:
11441         defaultlabel:
11442         if (gameMode == IcsExamining) {
11443             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11444                     PieceToChar(selection), AAA + x, ONE + y);
11445             SendToICS(buf);
11446         } else {
11447             boards[0][y][x] = selection;
11448             DrawPosition(FALSE, boards[0]);
11449         }
11450         break;
11451     }
11452 }
11453
11454
11455 void
11456 DropMenuEvent(selection, x, y)
11457      ChessSquare selection;
11458      int x, y;
11459 {
11460     ChessMove moveType;
11461
11462     switch (gameMode) {
11463       case IcsPlayingWhite:
11464       case MachinePlaysBlack:
11465         if (!WhiteOnMove(currentMove)) {
11466             DisplayMoveError(_("It is Black's turn"));
11467             return;
11468         }
11469         moveType = WhiteDrop;
11470         break;
11471       case IcsPlayingBlack:
11472       case MachinePlaysWhite:
11473         if (WhiteOnMove(currentMove)) {
11474             DisplayMoveError(_("It is White's turn"));
11475             return;
11476         }
11477         moveType = BlackDrop;
11478         break;
11479       case EditGame:
11480         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11481         break;
11482       default:
11483         return;
11484     }
11485
11486     if (moveType == BlackDrop && selection < BlackPawn) {
11487       selection = (ChessSquare) ((int) selection
11488                                  + (int) BlackPawn - (int) WhitePawn);
11489     }
11490     if (boards[currentMove][y][x] != EmptySquare) {
11491         DisplayMoveError(_("That square is occupied"));
11492         return;
11493     }
11494
11495     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11496 }
11497
11498 void
11499 AcceptEvent()
11500 {
11501     /* Accept a pending offer of any kind from opponent */
11502     
11503     if (appData.icsActive) {
11504         SendToICS(ics_prefix);
11505         SendToICS("accept\n");
11506     } else if (cmailMsgLoaded) {
11507         if (currentMove == cmailOldMove &&
11508             commentList[cmailOldMove] != NULL &&
11509             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11510                    "Black offers a draw" : "White offers a draw")) {
11511             TruncateGame();
11512             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11513             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11514         } else {
11515             DisplayError(_("There is no pending offer on this move"), 0);
11516             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11517         }
11518     } else {
11519         /* Not used for offers from chess program */
11520     }
11521 }
11522
11523 void
11524 DeclineEvent()
11525 {
11526     /* Decline a pending offer of any kind from opponent */
11527     
11528     if (appData.icsActive) {
11529         SendToICS(ics_prefix);
11530         SendToICS("decline\n");
11531     } else if (cmailMsgLoaded) {
11532         if (currentMove == cmailOldMove &&
11533             commentList[cmailOldMove] != NULL &&
11534             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11535                    "Black offers a draw" : "White offers a draw")) {
11536 #ifdef NOTDEF
11537             AppendComment(cmailOldMove, "Draw declined");
11538             DisplayComment(cmailOldMove - 1, "Draw declined");
11539 #endif /*NOTDEF*/
11540         } else {
11541             DisplayError(_("There is no pending offer on this move"), 0);
11542         }
11543     } else {
11544         /* Not used for offers from chess program */
11545     }
11546 }
11547
11548 void
11549 RematchEvent()
11550 {
11551     /* Issue ICS rematch command */
11552     if (appData.icsActive) {
11553         SendToICS(ics_prefix);
11554         SendToICS("rematch\n");
11555     }
11556 }
11557
11558 void
11559 CallFlagEvent()
11560 {
11561     /* Call your opponent's flag (claim a win on time) */
11562     if (appData.icsActive) {
11563         SendToICS(ics_prefix);
11564         SendToICS("flag\n");
11565     } else {
11566         switch (gameMode) {
11567           default:
11568             return;
11569           case MachinePlaysWhite:
11570             if (whiteFlag) {
11571                 if (blackFlag)
11572                   GameEnds(GameIsDrawn, "Both players ran out of time",
11573                            GE_PLAYER);
11574                 else
11575                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11576             } else {
11577                 DisplayError(_("Your opponent is not out of time"), 0);
11578             }
11579             break;
11580           case MachinePlaysBlack:
11581             if (blackFlag) {
11582                 if (whiteFlag)
11583                   GameEnds(GameIsDrawn, "Both players ran out of time",
11584                            GE_PLAYER);
11585                 else
11586                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11587             } else {
11588                 DisplayError(_("Your opponent is not out of time"), 0);
11589             }
11590             break;
11591         }
11592     }
11593 }
11594
11595 void
11596 DrawEvent()
11597 {
11598     /* Offer draw or accept pending draw offer from opponent */
11599     
11600     if (appData.icsActive) {
11601         /* Note: tournament rules require draw offers to be
11602            made after you make your move but before you punch
11603            your clock.  Currently ICS doesn't let you do that;
11604            instead, you immediately punch your clock after making
11605            a move, but you can offer a draw at any time. */
11606         
11607         SendToICS(ics_prefix);
11608         SendToICS("draw\n");
11609     } else if (cmailMsgLoaded) {
11610         if (currentMove == cmailOldMove &&
11611             commentList[cmailOldMove] != NULL &&
11612             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11613                    "Black offers a draw" : "White offers a draw")) {
11614             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11615             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11616         } else if (currentMove == cmailOldMove + 1) {
11617             char *offer = WhiteOnMove(cmailOldMove) ?
11618               "White offers a draw" : "Black offers a draw";
11619             AppendComment(currentMove, offer);
11620             DisplayComment(currentMove - 1, offer);
11621             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11622         } else {
11623             DisplayError(_("You must make your move before offering a draw"), 0);
11624             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11625         }
11626     } else if (first.offeredDraw) {
11627         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11628     } else {
11629         if (first.sendDrawOffers) {
11630             SendToProgram("draw\n", &first);
11631             userOfferedDraw = TRUE;
11632         }
11633     }
11634 }
11635
11636 void
11637 AdjournEvent()
11638 {
11639     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11640     
11641     if (appData.icsActive) {
11642         SendToICS(ics_prefix);
11643         SendToICS("adjourn\n");
11644     } else {
11645         /* Currently GNU Chess doesn't offer or accept Adjourns */
11646     }
11647 }
11648
11649
11650 void
11651 AbortEvent()
11652 {
11653     /* Offer Abort or accept pending Abort offer from opponent */
11654     
11655     if (appData.icsActive) {
11656         SendToICS(ics_prefix);
11657         SendToICS("abort\n");
11658     } else {
11659         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11660     }
11661 }
11662
11663 void
11664 ResignEvent()
11665 {
11666     /* Resign.  You can do this even if it's not your turn. */
11667     
11668     if (appData.icsActive) {
11669         SendToICS(ics_prefix);
11670         SendToICS("resign\n");
11671     } else {
11672         switch (gameMode) {
11673           case MachinePlaysWhite:
11674             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11675             break;
11676           case MachinePlaysBlack:
11677             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11678             break;
11679           case EditGame:
11680             if (cmailMsgLoaded) {
11681                 TruncateGame();
11682                 if (WhiteOnMove(cmailOldMove)) {
11683                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11684                 } else {
11685                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11686                 }
11687                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11688             }
11689             break;
11690           default:
11691             break;
11692         }
11693     }
11694 }
11695
11696
11697 void
11698 StopObservingEvent()
11699 {
11700     /* Stop observing current games */
11701     SendToICS(ics_prefix);
11702     SendToICS("unobserve\n");
11703 }
11704
11705 void
11706 StopExaminingEvent()
11707 {
11708     /* Stop observing current game */
11709     SendToICS(ics_prefix);
11710     SendToICS("unexamine\n");
11711 }
11712
11713 void
11714 ForwardInner(target)
11715      int target;
11716 {
11717     int limit;
11718
11719     if (appData.debugMode)
11720         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11721                 target, currentMove, forwardMostMove);
11722
11723     if (gameMode == EditPosition)
11724       return;
11725
11726     if (gameMode == PlayFromGameFile && !pausing)
11727       PauseEvent();
11728     
11729     if (gameMode == IcsExamining && pausing)
11730       limit = pauseExamForwardMostMove;
11731     else
11732       limit = forwardMostMove;
11733     
11734     if (target > limit) target = limit;
11735
11736     if (target > 0 && moveList[target - 1][0]) {
11737         int fromX, fromY, toX, toY;
11738         toX = moveList[target - 1][2] - AAA;
11739         toY = moveList[target - 1][3] - ONE;
11740         if (moveList[target - 1][1] == '@') {
11741             if (appData.highlightLastMove) {
11742                 SetHighlights(-1, -1, toX, toY);
11743             }
11744         } else {
11745             fromX = moveList[target - 1][0] - AAA;
11746             fromY = moveList[target - 1][1] - ONE;
11747             if (target == currentMove + 1) {
11748                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11749             }
11750             if (appData.highlightLastMove) {
11751                 SetHighlights(fromX, fromY, toX, toY);
11752             }
11753         }
11754     }
11755     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11756         gameMode == Training || gameMode == PlayFromGameFile || 
11757         gameMode == AnalyzeFile) {
11758         while (currentMove < target) {
11759             SendMoveToProgram(currentMove++, &first);
11760         }
11761     } else {
11762         currentMove = target;
11763     }
11764     
11765     if (gameMode == EditGame || gameMode == EndOfGame) {
11766         whiteTimeRemaining = timeRemaining[0][currentMove];
11767         blackTimeRemaining = timeRemaining[1][currentMove];
11768     }
11769     DisplayBothClocks();
11770     DisplayMove(currentMove - 1);
11771     DrawPosition(FALSE, boards[currentMove]);
11772     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11773     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11774         DisplayComment(currentMove - 1, commentList[currentMove]);
11775     }
11776 }
11777
11778
11779 void
11780 ForwardEvent()
11781 {
11782     if (gameMode == IcsExamining && !pausing) {
11783         SendToICS(ics_prefix);
11784         SendToICS("forward\n");
11785     } else {
11786         ForwardInner(currentMove + 1);
11787     }
11788 }
11789
11790 void
11791 ToEndEvent()
11792 {
11793     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11794         /* to optimze, we temporarily turn off analysis mode while we feed
11795          * the remaining moves to the engine. Otherwise we get analysis output
11796          * after each move.
11797          */ 
11798         if (first.analysisSupport) {
11799           SendToProgram("exit\nforce\n", &first);
11800           first.analyzing = FALSE;
11801         }
11802     }
11803         
11804     if (gameMode == IcsExamining && !pausing) {
11805         SendToICS(ics_prefix);
11806         SendToICS("forward 999999\n");
11807     } else {
11808         ForwardInner(forwardMostMove);
11809     }
11810
11811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11812         /* we have fed all the moves, so reactivate analysis mode */
11813         SendToProgram("analyze\n", &first);
11814         first.analyzing = TRUE;
11815         /*first.maybeThinking = TRUE;*/
11816         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11817     }
11818 }
11819
11820 void
11821 BackwardInner(target)
11822      int target;
11823 {
11824     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11825
11826     if (appData.debugMode)
11827         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11828                 target, currentMove, forwardMostMove);
11829
11830     if (gameMode == EditPosition) return;
11831     if (currentMove <= backwardMostMove) {
11832         ClearHighlights();
11833         DrawPosition(full_redraw, boards[currentMove]);
11834         return;
11835     }
11836     if (gameMode == PlayFromGameFile && !pausing)
11837       PauseEvent();
11838     
11839     if (moveList[target][0]) {
11840         int fromX, fromY, toX, toY;
11841         toX = moveList[target][2] - AAA;
11842         toY = moveList[target][3] - ONE;
11843         if (moveList[target][1] == '@') {
11844             if (appData.highlightLastMove) {
11845                 SetHighlights(-1, -1, toX, toY);
11846             }
11847         } else {
11848             fromX = moveList[target][0] - AAA;
11849             fromY = moveList[target][1] - ONE;
11850             if (target == currentMove - 1) {
11851                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11852             }
11853             if (appData.highlightLastMove) {
11854                 SetHighlights(fromX, fromY, toX, toY);
11855             }
11856         }
11857     }
11858     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11859         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11860         while (currentMove > target) {
11861             SendToProgram("undo\n", &first);
11862             currentMove--;
11863         }
11864     } else {
11865         currentMove = target;
11866     }
11867     
11868     if (gameMode == EditGame || gameMode == EndOfGame) {
11869         whiteTimeRemaining = timeRemaining[0][currentMove];
11870         blackTimeRemaining = timeRemaining[1][currentMove];
11871     }
11872     DisplayBothClocks();
11873     DisplayMove(currentMove - 1);
11874     DrawPosition(full_redraw, boards[currentMove]);
11875     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11876     // [HGM] PV info: routine tests if comment empty
11877     DisplayComment(currentMove - 1, commentList[currentMove]);
11878 }
11879
11880 void
11881 BackwardEvent()
11882 {
11883     if (gameMode == IcsExamining && !pausing) {
11884         SendToICS(ics_prefix);
11885         SendToICS("backward\n");
11886     } else {
11887         BackwardInner(currentMove - 1);
11888     }
11889 }
11890
11891 void
11892 ToStartEvent()
11893 {
11894     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11895         /* to optimize, we temporarily turn off analysis mode while we undo
11896          * all the moves. Otherwise we get analysis output after each undo.
11897          */ 
11898         if (first.analysisSupport) {
11899           SendToProgram("exit\nforce\n", &first);
11900           first.analyzing = FALSE;
11901         }
11902     }
11903
11904     if (gameMode == IcsExamining && !pausing) {
11905         SendToICS(ics_prefix);
11906         SendToICS("backward 999999\n");
11907     } else {
11908         BackwardInner(backwardMostMove);
11909     }
11910
11911     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11912         /* we have fed all the moves, so reactivate analysis mode */
11913         SendToProgram("analyze\n", &first);
11914         first.analyzing = TRUE;
11915         /*first.maybeThinking = TRUE;*/
11916         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11917     }
11918 }
11919
11920 void
11921 ToNrEvent(int to)
11922 {
11923   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11924   if (to >= forwardMostMove) to = forwardMostMove;
11925   if (to <= backwardMostMove) to = backwardMostMove;
11926   if (to < currentMove) {
11927     BackwardInner(to);
11928   } else {
11929     ForwardInner(to);
11930   }
11931 }
11932
11933 void
11934 RevertEvent()
11935 {
11936     if (gameMode != IcsExamining) {
11937         DisplayError(_("You are not examining a game"), 0);
11938         return;
11939     }
11940     if (pausing) {
11941         DisplayError(_("You can't revert while pausing"), 0);
11942         return;
11943     }
11944     SendToICS(ics_prefix);
11945     SendToICS("revert\n");
11946 }
11947
11948 void
11949 RetractMoveEvent()
11950 {
11951     switch (gameMode) {
11952       case MachinePlaysWhite:
11953       case MachinePlaysBlack:
11954         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11955             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11956             return;
11957         }
11958         if (forwardMostMove < 2) return;
11959         currentMove = forwardMostMove = forwardMostMove - 2;
11960         whiteTimeRemaining = timeRemaining[0][currentMove];
11961         blackTimeRemaining = timeRemaining[1][currentMove];
11962         DisplayBothClocks();
11963         DisplayMove(currentMove - 1);
11964         ClearHighlights();/*!! could figure this out*/
11965         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11966         SendToProgram("remove\n", &first);
11967         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11968         break;
11969
11970       case BeginningOfGame:
11971       default:
11972         break;
11973
11974       case IcsPlayingWhite:
11975       case IcsPlayingBlack:
11976         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11977             SendToICS(ics_prefix);
11978             SendToICS("takeback 2\n");
11979         } else {
11980             SendToICS(ics_prefix);
11981             SendToICS("takeback 1\n");
11982         }
11983         break;
11984     }
11985 }
11986
11987 void
11988 MoveNowEvent()
11989 {
11990     ChessProgramState *cps;
11991
11992     switch (gameMode) {
11993       case MachinePlaysWhite:
11994         if (!WhiteOnMove(forwardMostMove)) {
11995             DisplayError(_("It is your turn"), 0);
11996             return;
11997         }
11998         cps = &first;
11999         break;
12000       case MachinePlaysBlack:
12001         if (WhiteOnMove(forwardMostMove)) {
12002             DisplayError(_("It is your turn"), 0);
12003             return;
12004         }
12005         cps = &first;
12006         break;
12007       case TwoMachinesPlay:
12008         if (WhiteOnMove(forwardMostMove) ==
12009             (first.twoMachinesColor[0] == 'w')) {
12010             cps = &first;
12011         } else {
12012             cps = &second;
12013         }
12014         break;
12015       case BeginningOfGame:
12016       default:
12017         return;
12018     }
12019     SendToProgram("?\n", cps);
12020 }
12021
12022 void
12023 TruncateGameEvent()
12024 {
12025     EditGameEvent();
12026     if (gameMode != EditGame) return;
12027     TruncateGame();
12028 }
12029
12030 void
12031 TruncateGame()
12032 {
12033     if (forwardMostMove > currentMove) {
12034         if (gameInfo.resultDetails != NULL) {
12035             free(gameInfo.resultDetails);
12036             gameInfo.resultDetails = NULL;
12037             gameInfo.result = GameUnfinished;
12038         }
12039         forwardMostMove = currentMove;
12040         HistorySet(parseList, backwardMostMove, forwardMostMove,
12041                    currentMove-1);
12042     }
12043 }
12044
12045 void
12046 HintEvent()
12047 {
12048     if (appData.noChessProgram) return;
12049     switch (gameMode) {
12050       case MachinePlaysWhite:
12051         if (WhiteOnMove(forwardMostMove)) {
12052             DisplayError(_("Wait until your turn"), 0);
12053             return;
12054         }
12055         break;
12056       case BeginningOfGame:
12057       case MachinePlaysBlack:
12058         if (!WhiteOnMove(forwardMostMove)) {
12059             DisplayError(_("Wait until your turn"), 0);
12060             return;
12061         }
12062         break;
12063       default:
12064         DisplayError(_("No hint available"), 0);
12065         return;
12066     }
12067     SendToProgram("hint\n", &first);
12068     hintRequested = TRUE;
12069 }
12070
12071 void
12072 BookEvent()
12073 {
12074     if (appData.noChessProgram) return;
12075     switch (gameMode) {
12076       case MachinePlaysWhite:
12077         if (WhiteOnMove(forwardMostMove)) {
12078             DisplayError(_("Wait until your turn"), 0);
12079             return;
12080         }
12081         break;
12082       case BeginningOfGame:
12083       case MachinePlaysBlack:
12084         if (!WhiteOnMove(forwardMostMove)) {
12085             DisplayError(_("Wait until your turn"), 0);
12086             return;
12087         }
12088         break;
12089       case EditPosition:
12090         EditPositionDone(TRUE);
12091         break;
12092       case TwoMachinesPlay:
12093         return;
12094       default:
12095         break;
12096     }
12097     SendToProgram("bk\n", &first);
12098     bookOutput[0] = NULLCHAR;
12099     bookRequested = TRUE;
12100 }
12101
12102 void
12103 AboutGameEvent()
12104 {
12105     char *tags = PGNTags(&gameInfo);
12106     TagsPopUp(tags, CmailMsg());
12107     free(tags);
12108 }
12109
12110 /* end button procedures */
12111
12112 void
12113 PrintPosition(fp, move)
12114      FILE *fp;
12115      int move;
12116 {
12117     int i, j;
12118     
12119     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12120         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12121             char c = PieceToChar(boards[move][i][j]);
12122             fputc(c == 'x' ? '.' : c, fp);
12123             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12124         }
12125     }
12126     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12127       fprintf(fp, "white to play\n");
12128     else
12129       fprintf(fp, "black to play\n");
12130 }
12131
12132 void
12133 PrintOpponents(fp)
12134      FILE *fp;
12135 {
12136     if (gameInfo.white != NULL) {
12137         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12138     } else {
12139         fprintf(fp, "\n");
12140     }
12141 }
12142
12143 /* Find last component of program's own name, using some heuristics */
12144 void
12145 TidyProgramName(prog, host, buf)
12146      char *prog, *host, buf[MSG_SIZ];
12147 {
12148     char *p, *q;
12149     int local = (strcmp(host, "localhost") == 0);
12150     while (!local && (p = strchr(prog, ';')) != NULL) {
12151         p++;
12152         while (*p == ' ') p++;
12153         prog = p;
12154     }
12155     if (*prog == '"' || *prog == '\'') {
12156         q = strchr(prog + 1, *prog);
12157     } else {
12158         q = strchr(prog, ' ');
12159     }
12160     if (q == NULL) q = prog + strlen(prog);
12161     p = q;
12162     while (p >= prog && *p != '/' && *p != '\\') p--;
12163     p++;
12164     if(p == prog && *p == '"') p++;
12165     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12166     memcpy(buf, p, q - p);
12167     buf[q - p] = NULLCHAR;
12168     if (!local) {
12169         strcat(buf, "@");
12170         strcat(buf, host);
12171     }
12172 }
12173
12174 char *
12175 TimeControlTagValue()
12176 {
12177     char buf[MSG_SIZ];
12178     if (!appData.clockMode) {
12179         strcpy(buf, "-");
12180     } else if (movesPerSession > 0) {
12181         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12182     } else if (timeIncrement == 0) {
12183         sprintf(buf, "%ld", timeControl/1000);
12184     } else {
12185         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12186     }
12187     return StrSave(buf);
12188 }
12189
12190 void
12191 SetGameInfo()
12192 {
12193     /* This routine is used only for certain modes */
12194     VariantClass v = gameInfo.variant;
12195     ClearGameInfo(&gameInfo);
12196     gameInfo.variant = v;
12197
12198     switch (gameMode) {
12199       case MachinePlaysWhite:
12200         gameInfo.event = StrSave( appData.pgnEventHeader );
12201         gameInfo.site = StrSave(HostName());
12202         gameInfo.date = PGNDate();
12203         gameInfo.round = StrSave("-");
12204         gameInfo.white = StrSave(first.tidy);
12205         gameInfo.black = StrSave(UserName());
12206         gameInfo.timeControl = TimeControlTagValue();
12207         break;
12208
12209       case MachinePlaysBlack:
12210         gameInfo.event = StrSave( appData.pgnEventHeader );
12211         gameInfo.site = StrSave(HostName());
12212         gameInfo.date = PGNDate();
12213         gameInfo.round = StrSave("-");
12214         gameInfo.white = StrSave(UserName());
12215         gameInfo.black = StrSave(first.tidy);
12216         gameInfo.timeControl = TimeControlTagValue();
12217         break;
12218
12219       case TwoMachinesPlay:
12220         gameInfo.event = StrSave( appData.pgnEventHeader );
12221         gameInfo.site = StrSave(HostName());
12222         gameInfo.date = PGNDate();
12223         if (matchGame > 0) {
12224             char buf[MSG_SIZ];
12225             sprintf(buf, "%d", matchGame);
12226             gameInfo.round = StrSave(buf);
12227         } else {
12228             gameInfo.round = StrSave("-");
12229         }
12230         if (first.twoMachinesColor[0] == 'w') {
12231             gameInfo.white = StrSave(first.tidy);
12232             gameInfo.black = StrSave(second.tidy);
12233         } else {
12234             gameInfo.white = StrSave(second.tidy);
12235             gameInfo.black = StrSave(first.tidy);
12236         }
12237         gameInfo.timeControl = TimeControlTagValue();
12238         break;
12239
12240       case EditGame:
12241         gameInfo.event = StrSave("Edited game");
12242         gameInfo.site = StrSave(HostName());
12243         gameInfo.date = PGNDate();
12244         gameInfo.round = StrSave("-");
12245         gameInfo.white = StrSave("-");
12246         gameInfo.black = StrSave("-");
12247         break;
12248
12249       case EditPosition:
12250         gameInfo.event = StrSave("Edited position");
12251         gameInfo.site = StrSave(HostName());
12252         gameInfo.date = PGNDate();
12253         gameInfo.round = StrSave("-");
12254         gameInfo.white = StrSave("-");
12255         gameInfo.black = StrSave("-");
12256         break;
12257
12258       case IcsPlayingWhite:
12259       case IcsPlayingBlack:
12260       case IcsObserving:
12261       case IcsExamining:
12262         break;
12263
12264       case PlayFromGameFile:
12265         gameInfo.event = StrSave("Game from non-PGN file");
12266         gameInfo.site = StrSave(HostName());
12267         gameInfo.date = PGNDate();
12268         gameInfo.round = StrSave("-");
12269         gameInfo.white = StrSave("?");
12270         gameInfo.black = StrSave("?");
12271         break;
12272
12273       default:
12274         break;
12275     }
12276 }
12277
12278 void
12279 ReplaceComment(index, text)
12280      int index;
12281      char *text;
12282 {
12283     int len;
12284
12285     while (*text == '\n') text++;
12286     len = strlen(text);
12287     while (len > 0 && text[len - 1] == '\n') len--;
12288
12289     if (commentList[index] != NULL)
12290       free(commentList[index]);
12291
12292     if (len == 0) {
12293         commentList[index] = NULL;
12294         return;
12295     }
12296     commentList[index] = (char *) malloc(len + 2);
12297     strncpy(commentList[index], text, len);
12298     commentList[index][len] = '\n';
12299     commentList[index][len + 1] = NULLCHAR;
12300 }
12301
12302 void
12303 CrushCRs(text)
12304      char *text;
12305 {
12306   char *p = text;
12307   char *q = text;
12308   char ch;
12309
12310   do {
12311     ch = *p++;
12312     if (ch == '\r') continue;
12313     *q++ = ch;
12314   } while (ch != '\0');
12315 }
12316
12317 void
12318 AppendComment(index, text)
12319      int index;
12320      char *text;
12321 {
12322     int oldlen, len;
12323     char *old;
12324
12325     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12326
12327     CrushCRs(text);
12328     while (*text == '\n') text++;
12329     len = strlen(text);
12330     while (len > 0 && text[len - 1] == '\n') len--;
12331
12332     if (len == 0) return;
12333
12334     if (commentList[index] != NULL) {
12335         old = commentList[index];
12336         oldlen = strlen(old);
12337         commentList[index] = (char *) malloc(oldlen + len + 2);
12338         strcpy(commentList[index], old);
12339         free(old);
12340         strncpy(&commentList[index][oldlen], text, len);
12341         commentList[index][oldlen + len] = '\n';
12342         commentList[index][oldlen + len + 1] = NULLCHAR;
12343     } else {
12344         commentList[index] = (char *) malloc(len + 2);
12345         strncpy(commentList[index], text, len);
12346         commentList[index][len] = '\n';
12347         commentList[index][len + 1] = NULLCHAR;
12348     }
12349 }
12350
12351 static char * FindStr( char * text, char * sub_text )
12352 {
12353     char * result = strstr( text, sub_text );
12354
12355     if( result != NULL ) {
12356         result += strlen( sub_text );
12357     }
12358
12359     return result;
12360 }
12361
12362 /* [AS] Try to extract PV info from PGN comment */
12363 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12364 char *GetInfoFromComment( int index, char * text )
12365 {
12366     char * sep = text;
12367
12368     if( text != NULL && index > 0 ) {
12369         int score = 0;
12370         int depth = 0;
12371         int time = -1, sec = 0, deci;
12372         char * s_eval = FindStr( text, "[%eval " );
12373         char * s_emt = FindStr( text, "[%emt " );
12374
12375         if( s_eval != NULL || s_emt != NULL ) {
12376             /* New style */
12377             char delim;
12378
12379             if( s_eval != NULL ) {
12380                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12381                     return text;
12382                 }
12383
12384                 if( delim != ']' ) {
12385                     return text;
12386                 }
12387             }
12388
12389             if( s_emt != NULL ) {
12390             }
12391         }
12392         else {
12393             /* We expect something like: [+|-]nnn.nn/dd */
12394             int score_lo = 0;
12395
12396             sep = strchr( text, '/' );
12397             if( sep == NULL || sep < (text+4) ) {
12398                 return text;
12399             }
12400
12401             time = -1; sec = -1; deci = -1;
12402             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12403                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12404                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12405                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12406                 return text;
12407             }
12408
12409             if( score_lo < 0 || score_lo >= 100 ) {
12410                 return text;
12411             }
12412
12413             if(sec >= 0) time = 600*time + 10*sec; else
12414             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12415
12416             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12417
12418             /* [HGM] PV time: now locate end of PV info */
12419             while( *++sep >= '0' && *sep <= '9'); // strip depth
12420             if(time >= 0)
12421             while( *++sep >= '0' && *sep <= '9'); // strip time
12422             if(sec >= 0)
12423             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12424             if(deci >= 0)
12425             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12426             while(*sep == ' ') sep++;
12427         }
12428
12429         if( depth <= 0 ) {
12430             return text;
12431         }
12432
12433         if( time < 0 ) {
12434             time = -1;
12435         }
12436
12437         pvInfoList[index-1].depth = depth;
12438         pvInfoList[index-1].score = score;
12439         pvInfoList[index-1].time  = 10*time; // centi-sec
12440     }
12441     return sep;
12442 }
12443
12444 void
12445 SendToProgram(message, cps)
12446      char *message;
12447      ChessProgramState *cps;
12448 {
12449     int count, outCount, error;
12450     char buf[MSG_SIZ];
12451
12452     if (cps->pr == NULL) return;
12453     Attention(cps);
12454     
12455     if (appData.debugMode) {
12456         TimeMark now;
12457         GetTimeMark(&now);
12458         fprintf(debugFP, "%ld >%-6s: %s", 
12459                 SubtractTimeMarks(&now, &programStartTime),
12460                 cps->which, message);
12461     }
12462     
12463     count = strlen(message);
12464     outCount = OutputToProcess(cps->pr, message, count, &error);
12465     if (outCount < count && !exiting 
12466                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12467         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12468         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12469             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12470                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12471                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12472             } else {
12473                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12474             }
12475             gameInfo.resultDetails = StrSave(buf);
12476         }
12477         DisplayFatalError(buf, error, 1);
12478     }
12479 }
12480
12481 void
12482 ReceiveFromProgram(isr, closure, message, count, error)
12483      InputSourceRef isr;
12484      VOIDSTAR closure;
12485      char *message;
12486      int count;
12487      int error;
12488 {
12489     char *end_str;
12490     char buf[MSG_SIZ];
12491     ChessProgramState *cps = (ChessProgramState *)closure;
12492
12493     if (isr != cps->isr) return; /* Killed intentionally */
12494     if (count <= 0) {
12495         if (count == 0) {
12496             sprintf(buf,
12497                     _("Error: %s chess program (%s) exited unexpectedly"),
12498                     cps->which, cps->program);
12499         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12500                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12501                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12502                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12503                 } else {
12504                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12505                 }
12506                 gameInfo.resultDetails = StrSave(buf);
12507             }
12508             RemoveInputSource(cps->isr);
12509             DisplayFatalError(buf, 0, 1);
12510         } else {
12511             sprintf(buf,
12512                     _("Error reading from %s chess program (%s)"),
12513                     cps->which, cps->program);
12514             RemoveInputSource(cps->isr);
12515
12516             /* [AS] Program is misbehaving badly... kill it */
12517             if( count == -2 ) {
12518                 DestroyChildProcess( cps->pr, 9 );
12519                 cps->pr = NoProc;
12520             }
12521
12522             DisplayFatalError(buf, error, 1);
12523         }
12524         return;
12525     }
12526     
12527     if ((end_str = strchr(message, '\r')) != NULL)
12528       *end_str = NULLCHAR;
12529     if ((end_str = strchr(message, '\n')) != NULL)
12530       *end_str = NULLCHAR;
12531     
12532     if (appData.debugMode) {
12533         TimeMark now; int print = 1;
12534         char *quote = ""; char c; int i;
12535
12536         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12537                 char start = message[0];
12538                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12539                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12540                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12541                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12542                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12543                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12544                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12545                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12546                         { quote = "# "; print = (appData.engineComments == 2); }
12547                 message[0] = start; // restore original message
12548         }
12549         if(print) {
12550                 GetTimeMark(&now);
12551                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12552                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12553                         quote,
12554                         message);
12555         }
12556     }
12557
12558     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12559     if (appData.icsEngineAnalyze) {
12560         if (strstr(message, "whisper") != NULL ||
12561              strstr(message, "kibitz") != NULL || 
12562             strstr(message, "tellics") != NULL) return;
12563     }
12564
12565     HandleMachineMove(message, cps);
12566 }
12567
12568
12569 void
12570 SendTimeControl(cps, mps, tc, inc, sd, st)
12571      ChessProgramState *cps;
12572      int mps, inc, sd, st;
12573      long tc;
12574 {
12575     char buf[MSG_SIZ];
12576     int seconds;
12577
12578     if( timeControl_2 > 0 ) {
12579         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12580             tc = timeControl_2;
12581         }
12582     }
12583     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12584     inc /= cps->timeOdds;
12585     st  /= cps->timeOdds;
12586
12587     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12588
12589     if (st > 0) {
12590       /* Set exact time per move, normally using st command */
12591       if (cps->stKludge) {
12592         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12593         seconds = st % 60;
12594         if (seconds == 0) {
12595           sprintf(buf, "level 1 %d\n", st/60);
12596         } else {
12597           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12598         }
12599       } else {
12600         sprintf(buf, "st %d\n", st);
12601       }
12602     } else {
12603       /* Set conventional or incremental time control, using level command */
12604       if (seconds == 0) {
12605         /* Note old gnuchess bug -- minutes:seconds used to not work.
12606            Fixed in later versions, but still avoid :seconds
12607            when seconds is 0. */
12608         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12609       } else {
12610         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12611                 seconds, inc/1000);
12612       }
12613     }
12614     SendToProgram(buf, cps);
12615
12616     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12617     /* Orthogonally, limit search to given depth */
12618     if (sd > 0) {
12619       if (cps->sdKludge) {
12620         sprintf(buf, "depth\n%d\n", sd);
12621       } else {
12622         sprintf(buf, "sd %d\n", sd);
12623       }
12624       SendToProgram(buf, cps);
12625     }
12626
12627     if(cps->nps > 0) { /* [HGM] nps */
12628         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12629         else {
12630                 sprintf(buf, "nps %d\n", cps->nps);
12631               SendToProgram(buf, cps);
12632         }
12633     }
12634 }
12635
12636 ChessProgramState *WhitePlayer()
12637 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12638 {
12639     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12640        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12641         return &second;
12642     return &first;
12643 }
12644
12645 void
12646 SendTimeRemaining(cps, machineWhite)
12647      ChessProgramState *cps;
12648      int /*boolean*/ machineWhite;
12649 {
12650     char message[MSG_SIZ];
12651     long time, otime;
12652
12653     /* Note: this routine must be called when the clocks are stopped
12654        or when they have *just* been set or switched; otherwise
12655        it will be off by the time since the current tick started.
12656     */
12657     if (machineWhite) {
12658         time = whiteTimeRemaining / 10;
12659         otime = blackTimeRemaining / 10;
12660     } else {
12661         time = blackTimeRemaining / 10;
12662         otime = whiteTimeRemaining / 10;
12663     }
12664     /* [HGM] translate opponent's time by time-odds factor */
12665     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12666     if (appData.debugMode) {
12667         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12668     }
12669
12670     if (time <= 0) time = 1;
12671     if (otime <= 0) otime = 1;
12672     
12673     sprintf(message, "time %ld\n", time);
12674     SendToProgram(message, cps);
12675
12676     sprintf(message, "otim %ld\n", otime);
12677     SendToProgram(message, cps);
12678 }
12679
12680 int
12681 BoolFeature(p, name, loc, cps)
12682      char **p;
12683      char *name;
12684      int *loc;
12685      ChessProgramState *cps;
12686 {
12687   char buf[MSG_SIZ];
12688   int len = strlen(name);
12689   int val;
12690   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12691     (*p) += len + 1;
12692     sscanf(*p, "%d", &val);
12693     *loc = (val != 0);
12694     while (**p && **p != ' ') (*p)++;
12695     sprintf(buf, "accepted %s\n", name);
12696     SendToProgram(buf, cps);
12697     return TRUE;
12698   }
12699   return FALSE;
12700 }
12701
12702 int
12703 IntFeature(p, name, loc, cps)
12704      char **p;
12705      char *name;
12706      int *loc;
12707      ChessProgramState *cps;
12708 {
12709   char buf[MSG_SIZ];
12710   int len = strlen(name);
12711   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12712     (*p) += len + 1;
12713     sscanf(*p, "%d", loc);
12714     while (**p && **p != ' ') (*p)++;
12715     sprintf(buf, "accepted %s\n", name);
12716     SendToProgram(buf, cps);
12717     return TRUE;
12718   }
12719   return FALSE;
12720 }
12721
12722 int
12723 StringFeature(p, name, loc, cps)
12724      char **p;
12725      char *name;
12726      char loc[];
12727      ChessProgramState *cps;
12728 {
12729   char buf[MSG_SIZ];
12730   int len = strlen(name);
12731   if (strncmp((*p), name, len) == 0
12732       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12733     (*p) += len + 2;
12734     sscanf(*p, "%[^\"]", loc);
12735     while (**p && **p != '\"') (*p)++;
12736     if (**p == '\"') (*p)++;
12737     sprintf(buf, "accepted %s\n", name);
12738     SendToProgram(buf, cps);
12739     return TRUE;
12740   }
12741   return FALSE;
12742 }
12743
12744 int 
12745 ParseOption(Option *opt, ChessProgramState *cps)
12746 // [HGM] options: process the string that defines an engine option, and determine
12747 // name, type, default value, and allowed value range
12748 {
12749         char *p, *q, buf[MSG_SIZ];
12750         int n, min = (-1)<<31, max = 1<<31, def;
12751
12752         if(p = strstr(opt->name, " -spin ")) {
12753             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12754             if(max < min) max = min; // enforce consistency
12755             if(def < min) def = min;
12756             if(def > max) def = max;
12757             opt->value = def;
12758             opt->min = min;
12759             opt->max = max;
12760             opt->type = Spin;
12761         } else if((p = strstr(opt->name, " -slider "))) {
12762             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12763             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12764             if(max < min) max = min; // enforce consistency
12765             if(def < min) def = min;
12766             if(def > max) def = max;
12767             opt->value = def;
12768             opt->min = min;
12769             opt->max = max;
12770             opt->type = Spin; // Slider;
12771         } else if((p = strstr(opt->name, " -string "))) {
12772             opt->textValue = p+9;
12773             opt->type = TextBox;
12774         } else if((p = strstr(opt->name, " -file "))) {
12775             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12776             opt->textValue = p+7;
12777             opt->type = TextBox; // FileName;
12778         } else if((p = strstr(opt->name, " -path "))) {
12779             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12780             opt->textValue = p+7;
12781             opt->type = TextBox; // PathName;
12782         } else if(p = strstr(opt->name, " -check ")) {
12783             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12784             opt->value = (def != 0);
12785             opt->type = CheckBox;
12786         } else if(p = strstr(opt->name, " -combo ")) {
12787             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12788             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12789             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12790             opt->value = n = 0;
12791             while(q = StrStr(q, " /// ")) {
12792                 n++; *q = 0;    // count choices, and null-terminate each of them
12793                 q += 5;
12794                 if(*q == '*') { // remember default, which is marked with * prefix
12795                     q++;
12796                     opt->value = n;
12797                 }
12798                 cps->comboList[cps->comboCnt++] = q;
12799             }
12800             cps->comboList[cps->comboCnt++] = NULL;
12801             opt->max = n + 1;
12802             opt->type = ComboBox;
12803         } else if(p = strstr(opt->name, " -button")) {
12804             opt->type = Button;
12805         } else if(p = strstr(opt->name, " -save")) {
12806             opt->type = SaveButton;
12807         } else return FALSE;
12808         *p = 0; // terminate option name
12809         // now look if the command-line options define a setting for this engine option.
12810         if(cps->optionSettings && cps->optionSettings[0])
12811             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12812         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12813                 sprintf(buf, "option %s", p);
12814                 if(p = strstr(buf, ",")) *p = 0;
12815                 strcat(buf, "\n");
12816                 SendToProgram(buf, cps);
12817         }
12818         return TRUE;
12819 }
12820
12821 void
12822 FeatureDone(cps, val)
12823      ChessProgramState* cps;
12824      int val;
12825 {
12826   DelayedEventCallback cb = GetDelayedEvent();
12827   if ((cb == InitBackEnd3 && cps == &first) ||
12828       (cb == TwoMachinesEventIfReady && cps == &second)) {
12829     CancelDelayedEvent();
12830     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12831   }
12832   cps->initDone = val;
12833 }
12834
12835 /* Parse feature command from engine */
12836 void
12837 ParseFeatures(args, cps)
12838      char* args;
12839      ChessProgramState *cps;  
12840 {
12841   char *p = args;
12842   char *q;
12843   int val;
12844   char buf[MSG_SIZ];
12845
12846   for (;;) {
12847     while (*p == ' ') p++;
12848     if (*p == NULLCHAR) return;
12849
12850     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12851     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12852     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12853     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12854     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12855     if (BoolFeature(&p, "reuse", &val, cps)) {
12856       /* Engine can disable reuse, but can't enable it if user said no */
12857       if (!val) cps->reuse = FALSE;
12858       continue;
12859     }
12860     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12861     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12862       if (gameMode == TwoMachinesPlay) {
12863         DisplayTwoMachinesTitle();
12864       } else {
12865         DisplayTitle("");
12866       }
12867       continue;
12868     }
12869     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12870     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12871     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12872     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12873     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12874     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12875     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12876     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12877     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12878     if (IntFeature(&p, "done", &val, cps)) {
12879       FeatureDone(cps, val);
12880       continue;
12881     }
12882     /* Added by Tord: */
12883     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12884     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12885     /* End of additions by Tord */
12886
12887     /* [HGM] added features: */
12888     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12889     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12890     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12891     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12892     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12893     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12894     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12895         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12896             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12897             SendToProgram(buf, cps);
12898             continue;
12899         }
12900         if(cps->nrOptions >= MAX_OPTIONS) {
12901             cps->nrOptions--;
12902             sprintf(buf, "%s engine has too many options\n", cps->which);
12903             DisplayError(buf, 0);
12904         }
12905         continue;
12906     }
12907     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12908     /* End of additions by HGM */
12909
12910     /* unknown feature: complain and skip */
12911     q = p;
12912     while (*q && *q != '=') q++;
12913     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12914     SendToProgram(buf, cps);
12915     p = q;
12916     if (*p == '=') {
12917       p++;
12918       if (*p == '\"') {
12919         p++;
12920         while (*p && *p != '\"') p++;
12921         if (*p == '\"') p++;
12922       } else {
12923         while (*p && *p != ' ') p++;
12924       }
12925     }
12926   }
12927
12928 }
12929
12930 void
12931 PeriodicUpdatesEvent(newState)
12932      int newState;
12933 {
12934     if (newState == appData.periodicUpdates)
12935       return;
12936
12937     appData.periodicUpdates=newState;
12938
12939     /* Display type changes, so update it now */
12940 //    DisplayAnalysis();
12941
12942     /* Get the ball rolling again... */
12943     if (newState) {
12944         AnalysisPeriodicEvent(1);
12945         StartAnalysisClock();
12946     }
12947 }
12948
12949 void
12950 PonderNextMoveEvent(newState)
12951      int newState;
12952 {
12953     if (newState == appData.ponderNextMove) return;
12954     if (gameMode == EditPosition) EditPositionDone(TRUE);
12955     if (newState) {
12956         SendToProgram("hard\n", &first);
12957         if (gameMode == TwoMachinesPlay) {
12958             SendToProgram("hard\n", &second);
12959         }
12960     } else {
12961         SendToProgram("easy\n", &first);
12962         thinkOutput[0] = NULLCHAR;
12963         if (gameMode == TwoMachinesPlay) {
12964             SendToProgram("easy\n", &second);
12965         }
12966     }
12967     appData.ponderNextMove = newState;
12968 }
12969
12970 void
12971 NewSettingEvent(option, command, value)
12972      char *command;
12973      int option, value;
12974 {
12975     char buf[MSG_SIZ];
12976
12977     if (gameMode == EditPosition) EditPositionDone(TRUE);
12978     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12979     SendToProgram(buf, &first);
12980     if (gameMode == TwoMachinesPlay) {
12981         SendToProgram(buf, &second);
12982     }
12983 }
12984
12985 void
12986 ShowThinkingEvent()
12987 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12988 {
12989     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12990     int newState = appData.showThinking
12991         // [HGM] thinking: other features now need thinking output as well
12992         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12993     
12994     if (oldState == newState) return;
12995     oldState = newState;
12996     if (gameMode == EditPosition) EditPositionDone(TRUE);
12997     if (oldState) {
12998         SendToProgram("post\n", &first);
12999         if (gameMode == TwoMachinesPlay) {
13000             SendToProgram("post\n", &second);
13001         }
13002     } else {
13003         SendToProgram("nopost\n", &first);
13004         thinkOutput[0] = NULLCHAR;
13005         if (gameMode == TwoMachinesPlay) {
13006             SendToProgram("nopost\n", &second);
13007         }
13008     }
13009 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13010 }
13011
13012 void
13013 AskQuestionEvent(title, question, replyPrefix, which)
13014      char *title; char *question; char *replyPrefix; char *which;
13015 {
13016   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13017   if (pr == NoProc) return;
13018   AskQuestion(title, question, replyPrefix, pr);
13019 }
13020
13021 void
13022 DisplayMove(moveNumber)
13023      int moveNumber;
13024 {
13025     char message[MSG_SIZ];
13026     char res[MSG_SIZ];
13027     char cpThinkOutput[MSG_SIZ];
13028
13029     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13030     
13031     if (moveNumber == forwardMostMove - 1 || 
13032         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13033
13034         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13035
13036         if (strchr(cpThinkOutput, '\n')) {
13037             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13038         }
13039     } else {
13040         *cpThinkOutput = NULLCHAR;
13041     }
13042
13043     /* [AS] Hide thinking from human user */
13044     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13045         *cpThinkOutput = NULLCHAR;
13046         if( thinkOutput[0] != NULLCHAR ) {
13047             int i;
13048
13049             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13050                 cpThinkOutput[i] = '.';
13051             }
13052             cpThinkOutput[i] = NULLCHAR;
13053             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13054         }
13055     }
13056
13057     if (moveNumber == forwardMostMove - 1 &&
13058         gameInfo.resultDetails != NULL) {
13059         if (gameInfo.resultDetails[0] == NULLCHAR) {
13060             sprintf(res, " %s", PGNResult(gameInfo.result));
13061         } else {
13062             sprintf(res, " {%s} %s",
13063                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13064         }
13065     } else {
13066         res[0] = NULLCHAR;
13067     }
13068
13069     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13070         DisplayMessage(res, cpThinkOutput);
13071     } else {
13072         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13073                 WhiteOnMove(moveNumber) ? " " : ".. ",
13074                 parseList[moveNumber], res);
13075         DisplayMessage(message, cpThinkOutput);
13076     }
13077 }
13078
13079 void
13080 DisplayComment(moveNumber, text)
13081      int moveNumber;
13082      char *text;
13083 {
13084     char title[MSG_SIZ];
13085     char buf[8000]; // comment can be long!
13086     int score, depth;
13087     
13088     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13089       strcpy(title, "Comment");
13090     } else {
13091       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13092               WhiteOnMove(moveNumber) ? " " : ".. ",
13093               parseList[moveNumber]);
13094     }
13095     // [HGM] PV info: display PV info together with (or as) comment
13096     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13097       if(text == NULL) text = "";                                           
13098       score = pvInfoList[moveNumber].score;
13099       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13100               depth, (pvInfoList[moveNumber].time+50)/100, text);
13101       text = buf;
13102     }
13103     if (text != NULL && (appData.autoDisplayComment || commentUp))
13104         CommentPopUp(title, text);
13105 }
13106
13107 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13108  * might be busy thinking or pondering.  It can be omitted if your
13109  * gnuchess is configured to stop thinking immediately on any user
13110  * input.  However, that gnuchess feature depends on the FIONREAD
13111  * ioctl, which does not work properly on some flavors of Unix.
13112  */
13113 void
13114 Attention(cps)
13115      ChessProgramState *cps;
13116 {
13117 #if ATTENTION
13118     if (!cps->useSigint) return;
13119     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13120     switch (gameMode) {
13121       case MachinePlaysWhite:
13122       case MachinePlaysBlack:
13123       case TwoMachinesPlay:
13124       case IcsPlayingWhite:
13125       case IcsPlayingBlack:
13126       case AnalyzeMode:
13127       case AnalyzeFile:
13128         /* Skip if we know it isn't thinking */
13129         if (!cps->maybeThinking) return;
13130         if (appData.debugMode)
13131           fprintf(debugFP, "Interrupting %s\n", cps->which);
13132         InterruptChildProcess(cps->pr);
13133         cps->maybeThinking = FALSE;
13134         break;
13135       default:
13136         break;
13137     }
13138 #endif /*ATTENTION*/
13139 }
13140
13141 int
13142 CheckFlags()
13143 {
13144     if (whiteTimeRemaining <= 0) {
13145         if (!whiteFlag) {
13146             whiteFlag = TRUE;
13147             if (appData.icsActive) {
13148                 if (appData.autoCallFlag &&
13149                     gameMode == IcsPlayingBlack && !blackFlag) {
13150                   SendToICS(ics_prefix);
13151                   SendToICS("flag\n");
13152                 }
13153             } else {
13154                 if (blackFlag) {
13155                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13156                 } else {
13157                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13158                     if (appData.autoCallFlag) {
13159                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13160                         return TRUE;
13161                     }
13162                 }
13163             }
13164         }
13165     }
13166     if (blackTimeRemaining <= 0) {
13167         if (!blackFlag) {
13168             blackFlag = TRUE;
13169             if (appData.icsActive) {
13170                 if (appData.autoCallFlag &&
13171                     gameMode == IcsPlayingWhite && !whiteFlag) {
13172                   SendToICS(ics_prefix);
13173                   SendToICS("flag\n");
13174                 }
13175             } else {
13176                 if (whiteFlag) {
13177                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13178                 } else {
13179                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13180                     if (appData.autoCallFlag) {
13181                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13182                         return TRUE;
13183                     }
13184                 }
13185             }
13186         }
13187     }
13188     return FALSE;
13189 }
13190
13191 void
13192 CheckTimeControl()
13193 {
13194     if (!appData.clockMode || appData.icsActive ||
13195         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13196
13197     /*
13198      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13199      */
13200     if ( !WhiteOnMove(forwardMostMove) )
13201         /* White made time control */
13202         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13203         /* [HGM] time odds: correct new time quota for time odds! */
13204                                             / WhitePlayer()->timeOdds;
13205       else
13206         /* Black made time control */
13207         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13208                                             / WhitePlayer()->other->timeOdds;
13209 }
13210
13211 void
13212 DisplayBothClocks()
13213 {
13214     int wom = gameMode == EditPosition ?
13215       !blackPlaysFirst : WhiteOnMove(currentMove);
13216     DisplayWhiteClock(whiteTimeRemaining, wom);
13217     DisplayBlackClock(blackTimeRemaining, !wom);
13218 }
13219
13220
13221 /* Timekeeping seems to be a portability nightmare.  I think everyone
13222    has ftime(), but I'm really not sure, so I'm including some ifdefs
13223    to use other calls if you don't.  Clocks will be less accurate if
13224    you have neither ftime nor gettimeofday.
13225 */
13226
13227 /* VS 2008 requires the #include outside of the function */
13228 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13229 #include <sys/timeb.h>
13230 #endif
13231
13232 /* Get the current time as a TimeMark */
13233 void
13234 GetTimeMark(tm)
13235      TimeMark *tm;
13236 {
13237 #if HAVE_GETTIMEOFDAY
13238
13239     struct timeval timeVal;
13240     struct timezone timeZone;
13241
13242     gettimeofday(&timeVal, &timeZone);
13243     tm->sec = (long) timeVal.tv_sec; 
13244     tm->ms = (int) (timeVal.tv_usec / 1000L);
13245
13246 #else /*!HAVE_GETTIMEOFDAY*/
13247 #if HAVE_FTIME
13248
13249 // include <sys/timeb.h> / moved to just above start of function
13250     struct timeb timeB;
13251
13252     ftime(&timeB);
13253     tm->sec = (long) timeB.time;
13254     tm->ms = (int) timeB.millitm;
13255
13256 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13257     tm->sec = (long) time(NULL);
13258     tm->ms = 0;
13259 #endif
13260 #endif
13261 }
13262
13263 /* Return the difference in milliseconds between two
13264    time marks.  We assume the difference will fit in a long!
13265 */
13266 long
13267 SubtractTimeMarks(tm2, tm1)
13268      TimeMark *tm2, *tm1;
13269 {
13270     return 1000L*(tm2->sec - tm1->sec) +
13271            (long) (tm2->ms - tm1->ms);
13272 }
13273
13274
13275 /*
13276  * Code to manage the game clocks.
13277  *
13278  * In tournament play, black starts the clock and then white makes a move.
13279  * We give the human user a slight advantage if he is playing white---the
13280  * clocks don't run until he makes his first move, so it takes zero time.
13281  * Also, we don't account for network lag, so we could get out of sync
13282  * with GNU Chess's clock -- but then, referees are always right.  
13283  */
13284
13285 static TimeMark tickStartTM;
13286 static long intendedTickLength;
13287
13288 long
13289 NextTickLength(timeRemaining)
13290      long timeRemaining;
13291 {
13292     long nominalTickLength, nextTickLength;
13293
13294     if (timeRemaining > 0L && timeRemaining <= 10000L)
13295       nominalTickLength = 100L;
13296     else
13297       nominalTickLength = 1000L;
13298     nextTickLength = timeRemaining % nominalTickLength;
13299     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13300
13301     return nextTickLength;
13302 }
13303
13304 /* Adjust clock one minute up or down */
13305 void
13306 AdjustClock(Boolean which, int dir)
13307 {
13308     if(which) blackTimeRemaining += 60000*dir;
13309     else      whiteTimeRemaining += 60000*dir;
13310     DisplayBothClocks();
13311 }
13312
13313 /* Stop clocks and reset to a fresh time control */
13314 void
13315 ResetClocks() 
13316 {
13317     (void) StopClockTimer();
13318     if (appData.icsActive) {
13319         whiteTimeRemaining = blackTimeRemaining = 0;
13320     } else { /* [HGM] correct new time quote for time odds */
13321         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13322         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13323     }
13324     if (whiteFlag || blackFlag) {
13325         DisplayTitle("");
13326         whiteFlag = blackFlag = FALSE;
13327     }
13328     DisplayBothClocks();
13329 }
13330
13331 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13332
13333 /* Decrement running clock by amount of time that has passed */
13334 void
13335 DecrementClocks()
13336 {
13337     long timeRemaining;
13338     long lastTickLength, fudge;
13339     TimeMark now;
13340
13341     if (!appData.clockMode) return;
13342     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13343         
13344     GetTimeMark(&now);
13345
13346     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13347
13348     /* Fudge if we woke up a little too soon */
13349     fudge = intendedTickLength - lastTickLength;
13350     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13351
13352     if (WhiteOnMove(forwardMostMove)) {
13353         if(whiteNPS >= 0) lastTickLength = 0;
13354         timeRemaining = whiteTimeRemaining -= lastTickLength;
13355         DisplayWhiteClock(whiteTimeRemaining - fudge,
13356                           WhiteOnMove(currentMove));
13357     } else {
13358         if(blackNPS >= 0) lastTickLength = 0;
13359         timeRemaining = blackTimeRemaining -= lastTickLength;
13360         DisplayBlackClock(blackTimeRemaining - fudge,
13361                           !WhiteOnMove(currentMove));
13362     }
13363
13364     if (CheckFlags()) return;
13365         
13366     tickStartTM = now;
13367     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13368     StartClockTimer(intendedTickLength);
13369
13370     /* if the time remaining has fallen below the alarm threshold, sound the
13371      * alarm. if the alarm has sounded and (due to a takeback or time control
13372      * with increment) the time remaining has increased to a level above the
13373      * threshold, reset the alarm so it can sound again. 
13374      */
13375     
13376     if (appData.icsActive && appData.icsAlarm) {
13377
13378         /* make sure we are dealing with the user's clock */
13379         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13380                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13381            )) return;
13382
13383         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13384             alarmSounded = FALSE;
13385         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13386             PlayAlarmSound();
13387             alarmSounded = TRUE;
13388         }
13389     }
13390 }
13391
13392
13393 /* A player has just moved, so stop the previously running
13394    clock and (if in clock mode) start the other one.
13395    We redisplay both clocks in case we're in ICS mode, because
13396    ICS gives us an update to both clocks after every move.
13397    Note that this routine is called *after* forwardMostMove
13398    is updated, so the last fractional tick must be subtracted
13399    from the color that is *not* on move now.
13400 */
13401 void
13402 SwitchClocks(int newMoveNr)
13403 {
13404     long lastTickLength;
13405     TimeMark now;
13406     int flagged = FALSE;
13407
13408     GetTimeMark(&now);
13409
13410     if (StopClockTimer() && appData.clockMode) {
13411         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13412         if (!WhiteOnMove(forwardMostMove)) {
13413             if(blackNPS >= 0) lastTickLength = 0;
13414             blackTimeRemaining -= lastTickLength;
13415            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13416 //         if(pvInfoList[forwardMostMove-1].time == -1)
13417                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13418                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13419         } else {
13420            if(whiteNPS >= 0) lastTickLength = 0;
13421            whiteTimeRemaining -= lastTickLength;
13422            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13423 //         if(pvInfoList[forwardMostMove-1].time == -1)
13424                  pvInfoList[forwardMostMove-1].time = 
13425                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13426         }
13427         flagged = CheckFlags();
13428     }
13429     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13430     CheckTimeControl();
13431
13432     if (flagged || !appData.clockMode) return;
13433
13434     switch (gameMode) {
13435       case MachinePlaysBlack:
13436       case MachinePlaysWhite:
13437       case BeginningOfGame:
13438         if (pausing) return;
13439         break;
13440
13441       case EditGame:
13442       case PlayFromGameFile:
13443       case IcsExamining:
13444         return;
13445
13446       default:
13447         break;
13448     }
13449
13450     tickStartTM = now;
13451     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13452       whiteTimeRemaining : blackTimeRemaining);
13453     StartClockTimer(intendedTickLength);
13454 }
13455         
13456
13457 /* Stop both clocks */
13458 void
13459 StopClocks()
13460 {       
13461     long lastTickLength;
13462     TimeMark now;
13463
13464     if (!StopClockTimer()) return;
13465     if (!appData.clockMode) return;
13466
13467     GetTimeMark(&now);
13468
13469     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13470     if (WhiteOnMove(forwardMostMove)) {
13471         if(whiteNPS >= 0) lastTickLength = 0;
13472         whiteTimeRemaining -= lastTickLength;
13473         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13474     } else {
13475         if(blackNPS >= 0) lastTickLength = 0;
13476         blackTimeRemaining -= lastTickLength;
13477         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13478     }
13479     CheckFlags();
13480 }
13481         
13482 /* Start clock of player on move.  Time may have been reset, so
13483    if clock is already running, stop and restart it. */
13484 void
13485 StartClocks()
13486 {
13487     (void) StopClockTimer(); /* in case it was running already */
13488     DisplayBothClocks();
13489     if (CheckFlags()) return;
13490
13491     if (!appData.clockMode) return;
13492     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13493
13494     GetTimeMark(&tickStartTM);
13495     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13496       whiteTimeRemaining : blackTimeRemaining);
13497
13498    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13499     whiteNPS = blackNPS = -1; 
13500     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13501        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13502         whiteNPS = first.nps;
13503     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13504        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13505         blackNPS = first.nps;
13506     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13507         whiteNPS = second.nps;
13508     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13509         blackNPS = second.nps;
13510     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13511
13512     StartClockTimer(intendedTickLength);
13513 }
13514
13515 char *
13516 TimeString(ms)
13517      long ms;
13518 {
13519     long second, minute, hour, day;
13520     char *sign = "";
13521     static char buf[32];
13522     
13523     if (ms > 0 && ms <= 9900) {
13524       /* convert milliseconds to tenths, rounding up */
13525       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13526
13527       sprintf(buf, " %03.1f ", tenths/10.0);
13528       return buf;
13529     }
13530
13531     /* convert milliseconds to seconds, rounding up */
13532     /* use floating point to avoid strangeness of integer division
13533        with negative dividends on many machines */
13534     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13535
13536     if (second < 0) {
13537         sign = "-";
13538         second = -second;
13539     }
13540     
13541     day = second / (60 * 60 * 24);
13542     second = second % (60 * 60 * 24);
13543     hour = second / (60 * 60);
13544     second = second % (60 * 60);
13545     minute = second / 60;
13546     second = second % 60;
13547     
13548     if (day > 0)
13549       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13550               sign, day, hour, minute, second);
13551     else if (hour > 0)
13552       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13553     else
13554       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13555     
13556     return buf;
13557 }
13558
13559
13560 /*
13561  * This is necessary because some C libraries aren't ANSI C compliant yet.
13562  */
13563 char *
13564 StrStr(string, match)
13565      char *string, *match;
13566 {
13567     int i, length;
13568     
13569     length = strlen(match);
13570     
13571     for (i = strlen(string) - length; i >= 0; i--, string++)
13572       if (!strncmp(match, string, length))
13573         return string;
13574     
13575     return NULL;
13576 }
13577
13578 char *
13579 StrCaseStr(string, match)
13580      char *string, *match;
13581 {
13582     int i, j, length;
13583     
13584     length = strlen(match);
13585     
13586     for (i = strlen(string) - length; i >= 0; i--, string++) {
13587         for (j = 0; j < length; j++) {
13588             if (ToLower(match[j]) != ToLower(string[j]))
13589               break;
13590         }
13591         if (j == length) return string;
13592     }
13593
13594     return NULL;
13595 }
13596
13597 #ifndef _amigados
13598 int
13599 StrCaseCmp(s1, s2)
13600      char *s1, *s2;
13601 {
13602     char c1, c2;
13603     
13604     for (;;) {
13605         c1 = ToLower(*s1++);
13606         c2 = ToLower(*s2++);
13607         if (c1 > c2) return 1;
13608         if (c1 < c2) return -1;
13609         if (c1 == NULLCHAR) return 0;
13610     }
13611 }
13612
13613
13614 int
13615 ToLower(c)
13616      int c;
13617 {
13618     return isupper(c) ? tolower(c) : c;
13619 }
13620
13621
13622 int
13623 ToUpper(c)
13624      int c;
13625 {
13626     return islower(c) ? toupper(c) : c;
13627 }
13628 #endif /* !_amigados    */
13629
13630 char *
13631 StrSave(s)
13632      char *s;
13633 {
13634     char *ret;
13635
13636     if ((ret = (char *) malloc(strlen(s) + 1))) {
13637         strcpy(ret, s);
13638     }
13639     return ret;
13640 }
13641
13642 char *
13643 StrSavePtr(s, savePtr)
13644      char *s, **savePtr;
13645 {
13646     if (*savePtr) {
13647         free(*savePtr);
13648     }
13649     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13650         strcpy(*savePtr, s);
13651     }
13652     return(*savePtr);
13653 }
13654
13655 char *
13656 PGNDate()
13657 {
13658     time_t clock;
13659     struct tm *tm;
13660     char buf[MSG_SIZ];
13661
13662     clock = time((time_t *)NULL);
13663     tm = localtime(&clock);
13664     sprintf(buf, "%04d.%02d.%02d",
13665             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13666     return StrSave(buf);
13667 }
13668
13669
13670 char *
13671 PositionToFEN(move, overrideCastling)
13672      int move;
13673      char *overrideCastling;
13674 {
13675     int i, j, fromX, fromY, toX, toY;
13676     int whiteToPlay;
13677     char buf[128];
13678     char *p, *q;
13679     int emptycount;
13680     ChessSquare piece;
13681
13682     whiteToPlay = (gameMode == EditPosition) ?
13683       !blackPlaysFirst : (move % 2 == 0);
13684     p = buf;
13685
13686     /* Piece placement data */
13687     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13688         emptycount = 0;
13689         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13690             if (boards[move][i][j] == EmptySquare) {
13691                 emptycount++;
13692             } else { ChessSquare piece = boards[move][i][j];
13693                 if (emptycount > 0) {
13694                     if(emptycount<10) /* [HGM] can be >= 10 */
13695                         *p++ = '0' + emptycount;
13696                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13697                     emptycount = 0;
13698                 }
13699                 if(PieceToChar(piece) == '+') {
13700                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13701                     *p++ = '+';
13702                     piece = (ChessSquare)(DEMOTED piece);
13703                 } 
13704                 *p++ = PieceToChar(piece);
13705                 if(p[-1] == '~') {
13706                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13707                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13708                     *p++ = '~';
13709                 }
13710             }
13711         }
13712         if (emptycount > 0) {
13713             if(emptycount<10) /* [HGM] can be >= 10 */
13714                 *p++ = '0' + emptycount;
13715             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13716             emptycount = 0;
13717         }
13718         *p++ = '/';
13719     }
13720     *(p - 1) = ' ';
13721
13722     /* [HGM] print Crazyhouse or Shogi holdings */
13723     if( gameInfo.holdingsWidth ) {
13724         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13725         q = p;
13726         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13727             piece = boards[move][i][BOARD_WIDTH-1];
13728             if( piece != EmptySquare )
13729               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13730                   *p++ = PieceToChar(piece);
13731         }
13732         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13733             piece = boards[move][BOARD_HEIGHT-i-1][0];
13734             if( piece != EmptySquare )
13735               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13736                   *p++ = PieceToChar(piece);
13737         }
13738
13739         if( q == p ) *p++ = '-';
13740         *p++ = ']';
13741         *p++ = ' ';
13742     }
13743
13744     /* Active color */
13745     *p++ = whiteToPlay ? 'w' : 'b';
13746     *p++ = ' ';
13747
13748   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13749     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13750   } else {
13751   if(nrCastlingRights) {
13752      q = p;
13753      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13754        /* [HGM] write directly from rights */
13755            if(castlingRights[move][2] >= 0 &&
13756               castlingRights[move][0] >= 0   )
13757                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13758            if(castlingRights[move][2] >= 0 &&
13759               castlingRights[move][1] >= 0   )
13760                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13761            if(castlingRights[move][5] >= 0 &&
13762               castlingRights[move][3] >= 0   )
13763                 *p++ = castlingRights[move][3] + AAA;
13764            if(castlingRights[move][5] >= 0 &&
13765               castlingRights[move][4] >= 0   )
13766                 *p++ = castlingRights[move][4] + AAA;
13767      } else {
13768
13769         /* [HGM] write true castling rights */
13770         if( nrCastlingRights == 6 ) {
13771             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13772                castlingRights[move][2] >= 0  ) *p++ = 'K';
13773             if(castlingRights[move][1] == BOARD_LEFT &&
13774                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13775             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13776                castlingRights[move][5] >= 0  ) *p++ = 'k';
13777             if(castlingRights[move][4] == BOARD_LEFT &&
13778                castlingRights[move][5] >= 0  ) *p++ = 'q';
13779         }
13780      }
13781      if (q == p) *p++ = '-'; /* No castling rights */
13782      *p++ = ' ';
13783   }
13784
13785   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13786      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
13787     /* En passant target square */
13788     if (move > backwardMostMove) {
13789         fromX = moveList[move - 1][0] - AAA;
13790         fromY = moveList[move - 1][1] - ONE;
13791         toX = moveList[move - 1][2] - AAA;
13792         toY = moveList[move - 1][3] - ONE;
13793         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13794             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13795             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13796             fromX == toX) {
13797             /* 2-square pawn move just happened */
13798             *p++ = toX + AAA;
13799             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13800         } else {
13801             *p++ = '-';
13802         }
13803     } else if(move == backwardMostMove) {
13804         // [HGM] perhaps we should always do it like this, and forget the above?
13805         if(epStatus[move] >= 0) {
13806             *p++ = epStatus[move] + AAA;
13807             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13808         } else {
13809             *p++ = '-';
13810         }
13811     } else {
13812         *p++ = '-';
13813     }
13814     *p++ = ' ';
13815   }
13816   }
13817
13818     /* [HGM] find reversible plies */
13819     {   int i = 0, j=move;
13820
13821         if (appData.debugMode) { int k;
13822             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13823             for(k=backwardMostMove; k<=forwardMostMove; k++)
13824                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13825
13826         }
13827
13828         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13829         if( j == backwardMostMove ) i += initialRulePlies;
13830         sprintf(p, "%d ", i);
13831         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13832     }
13833     /* Fullmove number */
13834     sprintf(p, "%d", (move / 2) + 1);
13835     
13836     return StrSave(buf);
13837 }
13838
13839 Boolean
13840 ParseFEN(board, blackPlaysFirst, fen)
13841     Board board;
13842      int *blackPlaysFirst;
13843      char *fen;
13844 {
13845     int i, j;
13846     char *p;
13847     int emptycount;
13848     ChessSquare piece;
13849
13850     p = fen;
13851
13852     /* [HGM] by default clear Crazyhouse holdings, if present */
13853     if(gameInfo.holdingsWidth) {
13854        for(i=0; i<BOARD_HEIGHT; i++) {
13855            board[i][0]             = EmptySquare; /* black holdings */
13856            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13857            board[i][1]             = (ChessSquare) 0; /* black counts */
13858            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13859        }
13860     }
13861
13862     /* Piece placement data */
13863     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13864         j = 0;
13865         for (;;) {
13866             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13867                 if (*p == '/') p++;
13868                 emptycount = gameInfo.boardWidth - j;
13869                 while (emptycount--)
13870                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13871                 break;
13872 #if(BOARD_SIZE >= 10)
13873             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13874                 p++; emptycount=10;
13875                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13876                 while (emptycount--)
13877                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13878 #endif
13879             } else if (isdigit(*p)) {
13880                 emptycount = *p++ - '0';
13881                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13882                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13883                 while (emptycount--)
13884                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13885             } else if (*p == '+' || isalpha(*p)) {
13886                 if (j >= gameInfo.boardWidth) return FALSE;
13887                 if(*p=='+') {
13888                     piece = CharToPiece(*++p);
13889                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13890                     piece = (ChessSquare) (PROMOTED piece ); p++;
13891                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13892                 } else piece = CharToPiece(*p++);
13893
13894                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13895                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13896                     piece = (ChessSquare) (PROMOTED piece);
13897                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13898                     p++;
13899                 }
13900                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13901             } else {
13902                 return FALSE;
13903             }
13904         }
13905     }
13906     while (*p == '/' || *p == ' ') p++;
13907
13908     /* [HGM] look for Crazyhouse holdings here */
13909     while(*p==' ') p++;
13910     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13911         if(*p == '[') p++;
13912         if(*p == '-' ) *p++; /* empty holdings */ else {
13913             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13914             /* if we would allow FEN reading to set board size, we would   */
13915             /* have to add holdings and shift the board read so far here   */
13916             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13917                 *p++;
13918                 if((int) piece >= (int) BlackPawn ) {
13919                     i = (int)piece - (int)BlackPawn;
13920                     i = PieceToNumber((ChessSquare)i);
13921                     if( i >= gameInfo.holdingsSize ) return FALSE;
13922                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13923                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13924                 } else {
13925                     i = (int)piece - (int)WhitePawn;
13926                     i = PieceToNumber((ChessSquare)i);
13927                     if( i >= gameInfo.holdingsSize ) return FALSE;
13928                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13929                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13930                 }
13931             }
13932         }
13933         if(*p == ']') *p++;
13934     }
13935
13936     while(*p == ' ') p++;
13937
13938     /* Active color */
13939     switch (*p++) {
13940       case 'w':
13941         *blackPlaysFirst = FALSE;
13942         break;
13943       case 'b': 
13944         *blackPlaysFirst = TRUE;
13945         break;
13946       default:
13947         return FALSE;
13948     }
13949
13950     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13951     /* return the extra info in global variiables             */
13952
13953     /* set defaults in case FEN is incomplete */
13954     FENepStatus = EP_UNKNOWN;
13955     for(i=0; i<nrCastlingRights; i++ ) {
13956         FENcastlingRights[i] =
13957             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13958     }   /* assume possible unless obviously impossible */
13959     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13960     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13961     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13962                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13963     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13964     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13965     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13966                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13967     FENrulePlies = 0;
13968
13969     while(*p==' ') p++;
13970     if(nrCastlingRights) {
13971       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13972           /* castling indicator present, so default becomes no castlings */
13973           for(i=0; i<nrCastlingRights; i++ ) {
13974                  FENcastlingRights[i] = -1;
13975           }
13976       }
13977       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13978              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13979              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13980              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13981         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13982
13983         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13984             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13985             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13986         }
13987         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13988             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13989         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13990                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13991         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13992                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13993         switch(c) {
13994           case'K':
13995               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13996               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13997               FENcastlingRights[2] = whiteKingFile;
13998               break;
13999           case'Q':
14000               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14001               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14002               FENcastlingRights[2] = whiteKingFile;
14003               break;
14004           case'k':
14005               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
14006               FENcastlingRights[3] = i != blackKingFile ? i : -1;
14007               FENcastlingRights[5] = blackKingFile;
14008               break;
14009           case'q':
14010               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14011               FENcastlingRights[4] = i != blackKingFile ? i : -1;
14012               FENcastlingRights[5] = blackKingFile;
14013           case '-':
14014               break;
14015           default: /* FRC castlings */
14016               if(c >= 'a') { /* black rights */
14017                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14018                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14019                   if(i == BOARD_RGHT) break;
14020                   FENcastlingRights[5] = i;
14021                   c -= AAA;
14022                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14023                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14024                   if(c > i)
14025                       FENcastlingRights[3] = c;
14026                   else
14027                       FENcastlingRights[4] = c;
14028               } else { /* white rights */
14029                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14030                     if(board[0][i] == WhiteKing) break;
14031                   if(i == BOARD_RGHT) break;
14032                   FENcastlingRights[2] = i;
14033                   c -= AAA - 'a' + 'A';
14034                   if(board[0][c] >= WhiteKing) break;
14035                   if(c > i)
14036                       FENcastlingRights[0] = c;
14037                   else
14038                       FENcastlingRights[1] = c;
14039               }
14040         }
14041       }
14042       for(i=0; i<nrCastlingRights; i++)
14043         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14044     if (appData.debugMode) {
14045         fprintf(debugFP, "FEN castling rights:");
14046         for(i=0; i<nrCastlingRights; i++)
14047         fprintf(debugFP, " %d", FENcastlingRights[i]);
14048         fprintf(debugFP, "\n");
14049     }
14050
14051       while(*p==' ') p++;
14052     }
14053
14054     /* read e.p. field in games that know e.p. capture */
14055     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14056        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14057       if(*p=='-') {
14058         p++; FENepStatus = EP_NONE;
14059       } else {
14060          char c = *p++ - AAA;
14061
14062          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14063          if(*p >= '0' && *p <='9') *p++;
14064          FENepStatus = c;
14065       }
14066     }
14067
14068
14069     if(sscanf(p, "%d", &i) == 1) {
14070         FENrulePlies = i; /* 50-move ply counter */
14071         /* (The move number is still ignored)    */
14072     }
14073
14074     return TRUE;
14075 }
14076       
14077 void
14078 EditPositionPasteFEN(char *fen)
14079 {
14080   if (fen != NULL) {
14081     Board initial_position;
14082
14083     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14084       DisplayError(_("Bad FEN position in clipboard"), 0);
14085       return ;
14086     } else {
14087       int savedBlackPlaysFirst = blackPlaysFirst;
14088       EditPositionEvent();
14089       blackPlaysFirst = savedBlackPlaysFirst;
14090       CopyBoard(boards[0], initial_position);
14091           /* [HGM] copy FEN attributes as well */
14092           {   int i;
14093               initialRulePlies = FENrulePlies;
14094               epStatus[0] = FENepStatus;
14095               for( i=0; i<nrCastlingRights; i++ )
14096                   castlingRights[0][i] = FENcastlingRights[i];
14097           }
14098       EditPositionDone(FALSE);
14099       DisplayBothClocks();
14100       DrawPosition(FALSE, boards[currentMove]);
14101     }
14102   }
14103 }
14104
14105 static char cseq[12] = "\\   ";
14106
14107 Boolean set_cont_sequence(char *new_seq)
14108 {
14109     int len;
14110     Boolean ret;
14111
14112     // handle bad attempts to set the sequence
14113         if (!new_seq)
14114                 return 0; // acceptable error - no debug
14115
14116     len = strlen(new_seq);
14117     ret = (len > 0) && (len < sizeof(cseq));
14118     if (ret)
14119         strcpy(cseq, new_seq);
14120     else if (appData.debugMode)
14121         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14122     return ret;
14123 }
14124
14125 /*
14126     reformat a source message so words don't cross the width boundary.  internal
14127     newlines are not removed.  returns the wrapped size (no null character unless
14128     included in source message).  If dest is NULL, only calculate the size required
14129     for the dest buffer.  lp argument indicats line position upon entry, and it's
14130     passed back upon exit.
14131 */
14132 int wrap(char *dest, char *src, int count, int width, int *lp)
14133 {
14134     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14135
14136     cseq_len = strlen(cseq);
14137     old_line = line = *lp;
14138     ansi = len = clen = 0;
14139
14140     for (i=0; i < count; i++)
14141     {
14142         if (src[i] == '\033')
14143             ansi = 1;
14144
14145         // if we hit the width, back up
14146         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14147         {
14148             // store i & len in case the word is too long
14149             old_i = i, old_len = len;
14150
14151             // find the end of the last word
14152             while (i && src[i] != ' ' && src[i] != '\n')
14153             {
14154                 i--;
14155                 len--;
14156             }
14157
14158             // word too long?  restore i & len before splitting it
14159             if ((old_i-i+clen) >= width)
14160             {
14161                 i = old_i;
14162                 len = old_len;
14163             }
14164
14165             // extra space?
14166             if (i && src[i-1] == ' ')
14167                 len--;
14168
14169             if (src[i] != ' ' && src[i] != '\n')
14170             {
14171                 i--;
14172                 if (len)
14173                     len--;
14174             }
14175
14176             // now append the newline and continuation sequence
14177             if (dest)
14178                 dest[len] = '\n';
14179             len++;
14180             if (dest)
14181                 strncpy(dest+len, cseq, cseq_len);
14182             len += cseq_len;
14183             line = cseq_len;
14184             clen = cseq_len;
14185             continue;
14186         }
14187
14188         if (dest)
14189             dest[len] = src[i];
14190         len++;
14191         if (!ansi)
14192             line++;
14193         if (src[i] == '\n')
14194             line = 0;
14195         if (src[i] == 'm')
14196             ansi = 0;
14197     }
14198     if (dest && appData.debugMode)
14199     {
14200         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14201             count, width, line, len, *lp);
14202         show_bytes(debugFP, src, count);
14203         fprintf(debugFP, "\ndest: ");
14204         show_bytes(debugFP, dest, len);
14205         fprintf(debugFP, "\n");
14206     }
14207     *lp = dest ? line : old_line;
14208
14209     return len;
14210 }