aa91df7095658e61c4f55d4a622976e3b2e5c9cc
[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                 }
2517             } // [HGM] chat: end of patch
2518
2519             if (appData.zippyTalk || appData.zippyPlay) {
2520                 /* [DM] Backup address for color zippy lines */
2521                 backup = i;
2522 #if ZIPPY
2523        #ifdef WIN32
2524                if (loggedOn == TRUE)
2525                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2526                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2527        #else
2528                 if (ZippyControl(buf, &i) ||
2529                     ZippyConverse(buf, &i) ||
2530                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2531                       loggedOn = TRUE;
2532                       if (!appData.colorize) continue;
2533                 }
2534        #endif
2535 #endif
2536             } // [DM] 'else { ' deleted
2537                 if (
2538                     /* Regular tells and says */
2539                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2540                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2541                     looking_at(buf, &i, "* says: ") ||
2542                     /* Don't color "message" or "messages" output */
2543                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2544                     looking_at(buf, &i, "*. * at *:*: ") ||
2545                     looking_at(buf, &i, "--* (*:*): ") ||
2546                     /* Message notifications (same color as tells) */
2547                     looking_at(buf, &i, "* has left a message ") ||
2548                     looking_at(buf, &i, "* just sent you a message:\n") ||
2549                     /* Whispers and kibitzes */
2550                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2551                     looking_at(buf, &i, "* kibitzes: ") ||
2552                     /* Channel tells */
2553                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2554
2555                   if (tkind == 1 && strchr(star_match[0], ':')) {
2556                       /* Avoid "tells you:" spoofs in channels */
2557                      tkind = 3;
2558                   }
2559                   if (star_match[0][0] == NULLCHAR ||
2560                       strchr(star_match[0], ' ') ||
2561                       (tkind == 3 && strchr(star_match[1], ' '))) {
2562                     /* Reject bogus matches */
2563                     i = oldi;
2564                   } else {
2565                     if (appData.colorize) {
2566                       if (oldi > next_out) {
2567                         SendToPlayer(&buf[next_out], oldi - next_out);
2568                         next_out = oldi;
2569                       }
2570                       switch (tkind) {
2571                       case 1:
2572                         Colorize(ColorTell, FALSE);
2573                         curColor = ColorTell;
2574                         break;
2575                       case 2:
2576                         Colorize(ColorKibitz, FALSE);
2577                         curColor = ColorKibitz;
2578                         break;
2579                       case 3:
2580                         p = strrchr(star_match[1], '(');
2581                         if (p == NULL) {
2582                           p = star_match[1];
2583                         } else {
2584                           p++;
2585                         }
2586                         if (atoi(p) == 1) {
2587                           Colorize(ColorChannel1, FALSE);
2588                           curColor = ColorChannel1;
2589                         } else {
2590                           Colorize(ColorChannel, FALSE);
2591                           curColor = ColorChannel;
2592                         }
2593                         break;
2594                       case 5:
2595                         curColor = ColorNormal;
2596                         break;
2597                       }
2598                     }
2599                     if (started == STARTED_NONE && appData.autoComment &&
2600                         (gameMode == IcsObserving ||
2601                          gameMode == IcsPlayingWhite ||
2602                          gameMode == IcsPlayingBlack)) {
2603                       parse_pos = i - oldi;
2604                       memcpy(parse, &buf[oldi], parse_pos);
2605                       parse[parse_pos] = NULLCHAR;
2606                       started = STARTED_COMMENT;
2607                       savingComment = TRUE;
2608                     } else {
2609                       started = STARTED_CHATTER;
2610                       savingComment = FALSE;
2611                     }
2612                     loggedOn = TRUE;
2613                     continue;
2614                   }
2615                 }
2616
2617                 if (looking_at(buf, &i, "* s-shouts: ") ||
2618                     looking_at(buf, &i, "* c-shouts: ")) {
2619                     if (appData.colorize) {
2620                         if (oldi > next_out) {
2621                             SendToPlayer(&buf[next_out], oldi - next_out);
2622                             next_out = oldi;
2623                         }
2624                         Colorize(ColorSShout, FALSE);
2625                         curColor = ColorSShout;
2626                     }
2627                     loggedOn = TRUE;
2628                     started = STARTED_CHATTER;
2629                     continue;
2630                 }
2631
2632                 if (looking_at(buf, &i, "--->")) {
2633                     loggedOn = TRUE;
2634                     continue;
2635                 }
2636
2637                 if (looking_at(buf, &i, "* shouts: ") ||
2638                     looking_at(buf, &i, "--> ")) {
2639                     if (appData.colorize) {
2640                         if (oldi > next_out) {
2641                             SendToPlayer(&buf[next_out], oldi - next_out);
2642                             next_out = oldi;
2643                         }
2644                         Colorize(ColorShout, FALSE);
2645                         curColor = ColorShout;
2646                     }
2647                     loggedOn = TRUE;
2648                     started = STARTED_CHATTER;
2649                     continue;
2650                 }
2651
2652                 if (looking_at( buf, &i, "Challenge:")) {
2653                     if (appData.colorize) {
2654                         if (oldi > next_out) {
2655                             SendToPlayer(&buf[next_out], oldi - next_out);
2656                             next_out = oldi;
2657                         }
2658                         Colorize(ColorChallenge, FALSE);
2659                         curColor = ColorChallenge;
2660                     }
2661                     loggedOn = TRUE;
2662                     continue;
2663                 }
2664
2665                 if (looking_at(buf, &i, "* offers you") ||
2666                     looking_at(buf, &i, "* offers to be") ||
2667                     looking_at(buf, &i, "* would like to") ||
2668                     looking_at(buf, &i, "* requests to") ||
2669                     looking_at(buf, &i, "Your opponent offers") ||
2670                     looking_at(buf, &i, "Your opponent requests")) {
2671
2672                     if (appData.colorize) {
2673                         if (oldi > next_out) {
2674                             SendToPlayer(&buf[next_out], oldi - next_out);
2675                             next_out = oldi;
2676                         }
2677                         Colorize(ColorRequest, FALSE);
2678                         curColor = ColorRequest;
2679                     }
2680                     continue;
2681                 }
2682
2683                 if (looking_at(buf, &i, "* (*) seeking")) {
2684                     if (appData.colorize) {
2685                         if (oldi > next_out) {
2686                             SendToPlayer(&buf[next_out], oldi - next_out);
2687                             next_out = oldi;
2688                         }
2689                         Colorize(ColorSeek, FALSE);
2690                         curColor = ColorSeek;
2691                     }
2692                     continue;
2693             }
2694
2695             if (looking_at(buf, &i, "\\   ")) {
2696                 if (prevColor != ColorNormal) {
2697                     if (oldi > next_out) {
2698                         SendToPlayer(&buf[next_out], oldi - next_out);
2699                         next_out = oldi;
2700                     }
2701                     Colorize(prevColor, TRUE);
2702                     curColor = prevColor;
2703                 }
2704                 if (savingComment) {
2705                     parse_pos = i - oldi;
2706                     memcpy(parse, &buf[oldi], parse_pos);
2707                     parse[parse_pos] = NULLCHAR;
2708                     started = STARTED_COMMENT;
2709                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2710                         chattingPartner = savingComment - 3; // kludge to remember the box
2711                 } else {
2712                     started = STARTED_CHATTER;
2713                 }
2714                 continue;
2715             }
2716
2717             if (looking_at(buf, &i, "Black Strength :") ||
2718                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2719                 looking_at(buf, &i, "<10>") ||
2720                 looking_at(buf, &i, "#@#")) {
2721                 /* Wrong board style */
2722                 loggedOn = TRUE;
2723                 SendToICS(ics_prefix);
2724                 SendToICS("set style 12\n");
2725                 SendToICS(ics_prefix);
2726                 SendToICS("refresh\n");
2727                 continue;
2728             }
2729             
2730             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2731                 ICSInitScript();
2732                 have_sent_ICS_logon = 1;
2733                 continue;
2734             }
2735               
2736             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2737                 (looking_at(buf, &i, "\n<12> ") ||
2738                  looking_at(buf, &i, "<12> "))) {
2739                 loggedOn = TRUE;
2740                 if (oldi > next_out) {
2741                     SendToPlayer(&buf[next_out], oldi - next_out);
2742                 }
2743                 next_out = i;
2744                 started = STARTED_BOARD;
2745                 parse_pos = 0;
2746                 continue;
2747             }
2748
2749             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2750                 looking_at(buf, &i, "<b1> ")) {
2751                 if (oldi > next_out) {
2752                     SendToPlayer(&buf[next_out], oldi - next_out);
2753                 }
2754                 next_out = i;
2755                 started = STARTED_HOLDINGS;
2756                 parse_pos = 0;
2757                 continue;
2758             }
2759
2760             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2761                 loggedOn = TRUE;
2762                 /* Header for a move list -- first line */
2763
2764                 switch (ics_getting_history) {
2765                   case H_FALSE:
2766                     switch (gameMode) {
2767                       case IcsIdle:
2768                       case BeginningOfGame:
2769                         /* User typed "moves" or "oldmoves" while we
2770                            were idle.  Pretend we asked for these
2771                            moves and soak them up so user can step
2772                            through them and/or save them.
2773                            */
2774                         Reset(FALSE, TRUE);
2775                         gameMode = IcsObserving;
2776                         ModeHighlight();
2777                         ics_gamenum = -1;
2778                         ics_getting_history = H_GOT_UNREQ_HEADER;
2779                         break;
2780                       case EditGame: /*?*/
2781                       case EditPosition: /*?*/
2782                         /* Should above feature work in these modes too? */
2783                         /* For now it doesn't */
2784                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2785                         break;
2786                       default:
2787                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2788                         break;
2789                     }
2790                     break;
2791                   case H_REQUESTED:
2792                     /* Is this the right one? */
2793                     if (gameInfo.white && gameInfo.black &&
2794                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2795                         strcmp(gameInfo.black, star_match[2]) == 0) {
2796                         /* All is well */
2797                         ics_getting_history = H_GOT_REQ_HEADER;
2798                     }
2799                     break;
2800                   case H_GOT_REQ_HEADER:
2801                   case H_GOT_UNREQ_HEADER:
2802                   case H_GOT_UNWANTED_HEADER:
2803                   case H_GETTING_MOVES:
2804                     /* Should not happen */
2805                     DisplayError(_("Error gathering move list: two headers"), 0);
2806                     ics_getting_history = H_FALSE;
2807                     break;
2808                 }
2809
2810                 /* Save player ratings into gameInfo if needed */
2811                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2812                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2813                     (gameInfo.whiteRating == -1 ||
2814                      gameInfo.blackRating == -1)) {
2815
2816                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2817                     gameInfo.blackRating = string_to_rating(star_match[3]);
2818                     if (appData.debugMode)
2819                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2820                               gameInfo.whiteRating, gameInfo.blackRating);
2821                 }
2822                 continue;
2823             }
2824
2825             if (looking_at(buf, &i,
2826               "* * match, initial time: * minute*, increment: * second")) {
2827                 /* Header for a move list -- second line */
2828                 /* Initial board will follow if this is a wild game */
2829                 if (gameInfo.event != NULL) free(gameInfo.event);
2830                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2831                 gameInfo.event = StrSave(str);
2832                 /* [HGM] we switched variant. Translate boards if needed. */
2833                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2834                 continue;
2835             }
2836
2837             if (looking_at(buf, &i, "Move  ")) {
2838                 /* Beginning of a move list */
2839                 switch (ics_getting_history) {
2840                   case H_FALSE:
2841                     /* Normally should not happen */
2842                     /* Maybe user hit reset while we were parsing */
2843                     break;
2844                   case H_REQUESTED:
2845                     /* Happens if we are ignoring a move list that is not
2846                      * the one we just requested.  Common if the user
2847                      * tries to observe two games without turning off
2848                      * getMoveList */
2849                     break;
2850                   case H_GETTING_MOVES:
2851                     /* Should not happen */
2852                     DisplayError(_("Error gathering move list: nested"), 0);
2853                     ics_getting_history = H_FALSE;
2854                     break;
2855                   case H_GOT_REQ_HEADER:
2856                     ics_getting_history = H_GETTING_MOVES;
2857                     started = STARTED_MOVES;
2858                     parse_pos = 0;
2859                     if (oldi > next_out) {
2860                         SendToPlayer(&buf[next_out], oldi - next_out);
2861                     }
2862                     break;
2863                   case H_GOT_UNREQ_HEADER:
2864                     ics_getting_history = H_GETTING_MOVES;
2865                     started = STARTED_MOVES_NOHIDE;
2866                     parse_pos = 0;
2867                     break;
2868                   case H_GOT_UNWANTED_HEADER:
2869                     ics_getting_history = H_FALSE;
2870                     break;
2871                 }
2872                 continue;
2873             }                           
2874             
2875             if (looking_at(buf, &i, "% ") ||
2876                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2877                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2878                 if(suppressKibitz) next_out = i;
2879                 savingComment = FALSE;
2880                 suppressKibitz = 0;
2881                 switch (started) {
2882                   case STARTED_MOVES:
2883                   case STARTED_MOVES_NOHIDE:
2884                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2885                     parse[parse_pos + i - oldi] = NULLCHAR;
2886                     ParseGameHistory(parse);
2887 #if ZIPPY
2888                     if (appData.zippyPlay && first.initDone) {
2889                         FeedMovesToProgram(&first, forwardMostMove);
2890                         if (gameMode == IcsPlayingWhite) {
2891                             if (WhiteOnMove(forwardMostMove)) {
2892                                 if (first.sendTime) {
2893                                   if (first.useColors) {
2894                                     SendToProgram("black\n", &first); 
2895                                   }
2896                                   SendTimeRemaining(&first, TRUE);
2897                                 }
2898                                 if (first.useColors) {
2899                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2900                                 }
2901                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2902                                 first.maybeThinking = TRUE;
2903                             } else {
2904                                 if (first.usePlayother) {
2905                                   if (first.sendTime) {
2906                                     SendTimeRemaining(&first, TRUE);
2907                                   }
2908                                   SendToProgram("playother\n", &first);
2909                                   firstMove = FALSE;
2910                                 } else {
2911                                   firstMove = TRUE;
2912                                 }
2913                             }
2914                         } else if (gameMode == IcsPlayingBlack) {
2915                             if (!WhiteOnMove(forwardMostMove)) {
2916                                 if (first.sendTime) {
2917                                   if (first.useColors) {
2918                                     SendToProgram("white\n", &first);
2919                                   }
2920                                   SendTimeRemaining(&first, FALSE);
2921                                 }
2922                                 if (first.useColors) {
2923                                   SendToProgram("black\n", &first);
2924                                 }
2925                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2926                                 first.maybeThinking = TRUE;
2927                             } else {
2928                                 if (first.usePlayother) {
2929                                   if (first.sendTime) {
2930                                     SendTimeRemaining(&first, FALSE);
2931                                   }
2932                                   SendToProgram("playother\n", &first);
2933                                   firstMove = FALSE;
2934                                 } else {
2935                                   firstMove = TRUE;
2936                                 }
2937                             }
2938                         }                       
2939                     }
2940 #endif
2941                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2942                         /* Moves came from oldmoves or moves command
2943                            while we weren't doing anything else.
2944                            */
2945                         currentMove = forwardMostMove;
2946                         ClearHighlights();/*!!could figure this out*/
2947                         flipView = appData.flipView;
2948                         DrawPosition(TRUE, boards[currentMove]);
2949                         DisplayBothClocks();
2950                         sprintf(str, "%s vs. %s",
2951                                 gameInfo.white, gameInfo.black);
2952                         DisplayTitle(str);
2953                         gameMode = IcsIdle;
2954                     } else {
2955                         /* Moves were history of an active game */
2956                         if (gameInfo.resultDetails != NULL) {
2957                             free(gameInfo.resultDetails);
2958                             gameInfo.resultDetails = NULL;
2959                         }
2960                     }
2961                     HistorySet(parseList, backwardMostMove,
2962                                forwardMostMove, currentMove-1);
2963                     DisplayMove(currentMove - 1);
2964                     if (started == STARTED_MOVES) next_out = i;
2965                     started = STARTED_NONE;
2966                     ics_getting_history = H_FALSE;
2967                     break;
2968
2969                   case STARTED_OBSERVE:
2970                     started = STARTED_NONE;
2971                     SendToICS(ics_prefix);
2972                     SendToICS("refresh\n");
2973                     break;
2974
2975                   default:
2976                     break;
2977                 }
2978                 if(bookHit) { // [HGM] book: simulate book reply
2979                     static char bookMove[MSG_SIZ]; // a bit generous?
2980
2981                     programStats.nodes = programStats.depth = programStats.time = 
2982                     programStats.score = programStats.got_only_move = 0;
2983                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2984
2985                     strcpy(bookMove, "move ");
2986                     strcat(bookMove, bookHit);
2987                     HandleMachineMove(bookMove, &first);
2988                 }
2989                 continue;
2990             }
2991             
2992             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2993                  started == STARTED_HOLDINGS ||
2994                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2995                 /* Accumulate characters in move list or board */
2996                 parse[parse_pos++] = buf[i];
2997             }
2998             
2999             /* Start of game messages.  Mostly we detect start of game
3000                when the first board image arrives.  On some versions
3001                of the ICS, though, we need to do a "refresh" after starting
3002                to observe in order to get the current board right away. */
3003             if (looking_at(buf, &i, "Adding game * to observation list")) {
3004                 started = STARTED_OBSERVE;
3005                 continue;
3006             }
3007
3008             /* Handle auto-observe */
3009             if (appData.autoObserve &&
3010                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3011                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3012                 char *player;
3013                 /* Choose the player that was highlighted, if any. */
3014                 if (star_match[0][0] == '\033' ||
3015                     star_match[1][0] != '\033') {
3016                     player = star_match[0];
3017                 } else {
3018                     player = star_match[2];
3019                 }
3020                 sprintf(str, "%sobserve %s\n",
3021                         ics_prefix, StripHighlightAndTitle(player));
3022                 SendToICS(str);
3023
3024                 /* Save ratings from notify string */
3025                 strcpy(player1Name, star_match[0]);
3026                 player1Rating = string_to_rating(star_match[1]);
3027                 strcpy(player2Name, star_match[2]);
3028                 player2Rating = string_to_rating(star_match[3]);
3029
3030                 if (appData.debugMode)
3031                   fprintf(debugFP, 
3032                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3033                           player1Name, player1Rating,
3034                           player2Name, player2Rating);
3035
3036                 continue;
3037             }
3038
3039             /* Deal with automatic examine mode after a game,
3040                and with IcsObserving -> IcsExamining transition */
3041             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3042                 looking_at(buf, &i, "has made you an examiner of game *")) {
3043
3044                 int gamenum = atoi(star_match[0]);
3045                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3046                     gamenum == ics_gamenum) {
3047                     /* We were already playing or observing this game;
3048                        no need to refetch history */
3049                     gameMode = IcsExamining;
3050                     if (pausing) {
3051                         pauseExamForwardMostMove = forwardMostMove;
3052                     } else if (currentMove < forwardMostMove) {
3053                         ForwardInner(forwardMostMove);
3054                     }
3055                 } else {
3056                     /* I don't think this case really can happen */
3057                     SendToICS(ics_prefix);
3058                     SendToICS("refresh\n");
3059                 }
3060                 continue;
3061             }    
3062             
3063             /* Error messages */
3064 //          if (ics_user_moved) {
3065             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3066                 if (looking_at(buf, &i, "Illegal move") ||
3067                     looking_at(buf, &i, "Not a legal move") ||
3068                     looking_at(buf, &i, "Your king is in check") ||
3069                     looking_at(buf, &i, "It isn't your turn") ||
3070                     looking_at(buf, &i, "It is not your move")) {
3071                     /* Illegal move */
3072                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3073                         currentMove = forwardMostMove-1;
3074                         DisplayMove(currentMove - 1); /* before DMError */
3075                         DrawPosition(FALSE, boards[currentMove]);
3076                         SwitchClocks(forwardMostMove-1); // [HGM] race
3077                         DisplayBothClocks();
3078                     }
3079                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3080                     ics_user_moved = 0;
3081                     continue;
3082                 }
3083             }
3084
3085             if (looking_at(buf, &i, "still have time") ||
3086                 looking_at(buf, &i, "not out of time") ||
3087                 looking_at(buf, &i, "either player is out of time") ||
3088                 looking_at(buf, &i, "has timeseal; checking")) {
3089                 /* We must have called his flag a little too soon */
3090                 whiteFlag = blackFlag = FALSE;
3091                 continue;
3092             }
3093
3094             if (looking_at(buf, &i, "added * seconds to") ||
3095                 looking_at(buf, &i, "seconds were added to")) {
3096                 /* Update the clocks */
3097                 SendToICS(ics_prefix);
3098                 SendToICS("refresh\n");
3099                 continue;
3100             }
3101
3102             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3103                 ics_clock_paused = TRUE;
3104                 StopClocks();
3105                 continue;
3106             }
3107
3108             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3109                 ics_clock_paused = FALSE;
3110                 StartClocks();
3111                 continue;
3112             }
3113
3114             /* Grab player ratings from the Creating: message.
3115                Note we have to check for the special case when
3116                the ICS inserts things like [white] or [black]. */
3117             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3118                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3119                 /* star_matches:
3120                    0    player 1 name (not necessarily white)
3121                    1    player 1 rating
3122                    2    empty, white, or black (IGNORED)
3123                    3    player 2 name (not necessarily black)
3124                    4    player 2 rating
3125                    
3126                    The names/ratings are sorted out when the game
3127                    actually starts (below).
3128                 */
3129                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3130                 player1Rating = string_to_rating(star_match[1]);
3131                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3132                 player2Rating = string_to_rating(star_match[4]);
3133
3134                 if (appData.debugMode)
3135                   fprintf(debugFP, 
3136                           "Ratings from 'Creating:' %s %d, %s %d\n",
3137                           player1Name, player1Rating,
3138                           player2Name, player2Rating);
3139
3140                 continue;
3141             }
3142             
3143             /* Improved generic start/end-of-game messages */
3144             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3145                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3146                 /* If tkind == 0: */
3147                 /* star_match[0] is the game number */
3148                 /*           [1] is the white player's name */
3149                 /*           [2] is the black player's name */
3150                 /* For end-of-game: */
3151                 /*           [3] is the reason for the game end */
3152                 /*           [4] is a PGN end game-token, preceded by " " */
3153                 /* For start-of-game: */
3154                 /*           [3] begins with "Creating" or "Continuing" */
3155                 /*           [4] is " *" or empty (don't care). */
3156                 int gamenum = atoi(star_match[0]);
3157                 char *whitename, *blackname, *why, *endtoken;
3158                 ChessMove endtype = (ChessMove) 0;
3159
3160                 if (tkind == 0) {
3161                   whitename = star_match[1];
3162                   blackname = star_match[2];
3163                   why = star_match[3];
3164                   endtoken = star_match[4];
3165                 } else {
3166                   whitename = star_match[1];
3167                   blackname = star_match[3];
3168                   why = star_match[5];
3169                   endtoken = star_match[6];
3170                 }
3171
3172                 /* Game start messages */
3173                 if (strncmp(why, "Creating ", 9) == 0 ||
3174                     strncmp(why, "Continuing ", 11) == 0) {
3175                     gs_gamenum = gamenum;
3176                     strcpy(gs_kind, strchr(why, ' ') + 1);
3177                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3178 #if ZIPPY
3179                     if (appData.zippyPlay) {
3180                         ZippyGameStart(whitename, blackname);
3181                     }
3182 #endif /*ZIPPY*/
3183                     continue;
3184                 }
3185
3186                 /* Game end messages */
3187                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3188                     ics_gamenum != gamenum) {
3189                     continue;
3190                 }
3191                 while (endtoken[0] == ' ') endtoken++;
3192                 switch (endtoken[0]) {
3193                   case '*':
3194                   default:
3195                     endtype = GameUnfinished;
3196                     break;
3197                   case '0':
3198                     endtype = BlackWins;
3199                     break;
3200                   case '1':
3201                     if (endtoken[1] == '/')
3202                       endtype = GameIsDrawn;
3203                     else
3204                       endtype = WhiteWins;
3205                     break;
3206                 }
3207                 GameEnds(endtype, why, GE_ICS);
3208 #if ZIPPY
3209                 if (appData.zippyPlay && first.initDone) {
3210                     ZippyGameEnd(endtype, why);
3211                     if (first.pr == NULL) {
3212                       /* Start the next process early so that we'll
3213                          be ready for the next challenge */
3214                       StartChessProgram(&first);
3215                     }
3216                     /* Send "new" early, in case this command takes
3217                        a long time to finish, so that we'll be ready
3218                        for the next challenge. */
3219                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3220                     Reset(TRUE, TRUE);
3221                 }
3222 #endif /*ZIPPY*/
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i, "Removing game * from observation") ||
3227                 looking_at(buf, &i, "no longer observing game *") ||
3228                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3229                 if (gameMode == IcsObserving &&
3230                     atoi(star_match[0]) == ics_gamenum)
3231                   {
3232                       /* icsEngineAnalyze */
3233                       if (appData.icsEngineAnalyze) {
3234                             ExitAnalyzeMode();
3235                             ModeHighlight();
3236                       }
3237                       StopClocks();
3238                       gameMode = IcsIdle;
3239                       ics_gamenum = -1;
3240                       ics_user_moved = FALSE;
3241                   }
3242                 continue;
3243             }
3244
3245             if (looking_at(buf, &i, "no longer examining game *")) {
3246                 if (gameMode == IcsExamining &&
3247                     atoi(star_match[0]) == ics_gamenum)
3248                   {
3249                       gameMode = IcsIdle;
3250                       ics_gamenum = -1;
3251                       ics_user_moved = FALSE;
3252                   }
3253                 continue;
3254             }
3255
3256             /* Advance leftover_start past any newlines we find,
3257                so only partial lines can get reparsed */
3258             if (looking_at(buf, &i, "\n")) {
3259                 prevColor = curColor;
3260                 if (curColor != ColorNormal) {
3261                     if (oldi > next_out) {
3262                         SendToPlayer(&buf[next_out], oldi - next_out);
3263                         next_out = oldi;
3264                     }
3265                     Colorize(ColorNormal, FALSE);
3266                     curColor = ColorNormal;
3267                 }
3268                 if (started == STARTED_BOARD) {
3269                     started = STARTED_NONE;
3270                     parse[parse_pos] = NULLCHAR;
3271                     ParseBoard12(parse);
3272                     ics_user_moved = 0;
3273
3274                     /* Send premove here */
3275                     if (appData.premove) {
3276                       char str[MSG_SIZ];
3277                       if (currentMove == 0 &&
3278                           gameMode == IcsPlayingWhite &&
3279                           appData.premoveWhite) {
3280                         sprintf(str, "%s\n", appData.premoveWhiteText);
3281                         if (appData.debugMode)
3282                           fprintf(debugFP, "Sending premove:\n");
3283                         SendToICS(str);
3284                       } else if (currentMove == 1 &&
3285                                  gameMode == IcsPlayingBlack &&
3286                                  appData.premoveBlack) {
3287                         sprintf(str, "%s\n", appData.premoveBlackText);
3288                         if (appData.debugMode)
3289                           fprintf(debugFP, "Sending premove:\n");
3290                         SendToICS(str);
3291                       } else if (gotPremove) {
3292                         gotPremove = 0;
3293                         ClearPremoveHighlights();
3294                         if (appData.debugMode)
3295                           fprintf(debugFP, "Sending premove:\n");
3296                           UserMoveEvent(premoveFromX, premoveFromY, 
3297                                         premoveToX, premoveToY, 
3298                                         premovePromoChar);
3299                       }
3300                     }
3301
3302                     /* Usually suppress following prompt */
3303                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3304                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3305                         if (looking_at(buf, &i, "*% ")) {
3306                             savingComment = FALSE;
3307                             suppressKibitz = 0;
3308                         }
3309                     }
3310                     next_out = i;
3311                 } else if (started == STARTED_HOLDINGS) {
3312                     int gamenum;
3313                     char new_piece[MSG_SIZ];
3314                     started = STARTED_NONE;
3315                     parse[parse_pos] = NULLCHAR;
3316                     if (appData.debugMode)
3317                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3318                                                         parse, currentMove);
3319                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3320                         gamenum == ics_gamenum) {
3321                         if (gameInfo.variant == VariantNormal) {
3322                           /* [HGM] We seem to switch variant during a game!
3323                            * Presumably no holdings were displayed, so we have
3324                            * to move the position two files to the right to
3325                            * create room for them!
3326                            */
3327                           VariantClass newVariant;
3328                           switch(gameInfo.boardWidth) { // base guess on board width
3329                                 case 9:  newVariant = VariantShogi; break;
3330                                 case 10: newVariant = VariantGreat; break;
3331                                 default: newVariant = VariantCrazyhouse; break;
3332                           }
3333                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3334                           /* Get a move list just to see the header, which
3335                              will tell us whether this is really bug or zh */
3336                           if (ics_getting_history == H_FALSE) {
3337                             ics_getting_history = H_REQUESTED;
3338                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3339                             SendToICS(str);
3340                           }
3341                         }
3342                         new_piece[0] = NULLCHAR;
3343                         sscanf(parse, "game %d white [%s black [%s <- %s",
3344                                &gamenum, white_holding, black_holding,
3345                                new_piece);
3346                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3347                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3348                         /* [HGM] copy holdings to board holdings area */
3349                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3350                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3351                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3352 #if ZIPPY
3353                         if (appData.zippyPlay && first.initDone) {
3354                             ZippyHoldings(white_holding, black_holding,
3355                                           new_piece);
3356                         }
3357 #endif /*ZIPPY*/
3358                         if (tinyLayout || smallLayout) {
3359                             char wh[16], bh[16];
3360                             PackHolding(wh, white_holding);
3361                             PackHolding(bh, black_holding);
3362                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3363                                     gameInfo.white, gameInfo.black);
3364                         } else {
3365                             sprintf(str, "%s [%s] vs. %s [%s]",
3366                                     gameInfo.white, white_holding,
3367                                     gameInfo.black, black_holding);
3368                         }
3369
3370                         DrawPosition(FALSE, boards[currentMove]);
3371                         DisplayTitle(str);
3372                     }
3373                     /* Suppress following prompt */
3374                     if (looking_at(buf, &i, "*% ")) {
3375                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3376                         savingComment = FALSE;
3377                         suppressKibitz = 0;
3378                     }
3379                     next_out = i;
3380                 }
3381                 continue;
3382             }
3383
3384             i++;                /* skip unparsed character and loop back */
3385         }
3386         
3387         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3388 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3389 //          SendToPlayer(&buf[next_out], i - next_out);
3390             started != STARTED_HOLDINGS && leftover_start > next_out) {
3391             SendToPlayer(&buf[next_out], leftover_start - next_out);
3392             next_out = i;
3393         }
3394         
3395         leftover_len = buf_len - leftover_start;
3396         /* if buffer ends with something we couldn't parse,
3397            reparse it after appending the next read */
3398         
3399     } else if (count == 0) {
3400         RemoveInputSource(isr);
3401         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3402     } else {
3403         DisplayFatalError(_("Error reading from ICS"), error, 1);
3404     }
3405 }
3406
3407
3408 /* Board style 12 looks like this:
3409    
3410    <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
3411    
3412  * The "<12> " is stripped before it gets to this routine.  The two
3413  * trailing 0's (flip state and clock ticking) are later addition, and
3414  * some chess servers may not have them, or may have only the first.
3415  * Additional trailing fields may be added in the future.  
3416  */
3417
3418 #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"
3419
3420 #define RELATION_OBSERVING_PLAYED    0
3421 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3422 #define RELATION_PLAYING_MYMOVE      1
3423 #define RELATION_PLAYING_NOTMYMOVE  -1
3424 #define RELATION_EXAMINING           2
3425 #define RELATION_ISOLATED_BOARD     -3
3426 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3427
3428 void
3429 ParseBoard12(string)
3430      char *string;
3431
3432     GameMode newGameMode;
3433     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3434     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3435     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3436     char to_play, board_chars[200];
3437     char move_str[500], str[500], elapsed_time[500];
3438     char black[32], white[32];
3439     Board board;
3440     int prevMove = currentMove;
3441     int ticking = 2;
3442     ChessMove moveType;
3443     int fromX, fromY, toX, toY;
3444     char promoChar;
3445     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3446     char *bookHit = NULL; // [HGM] book
3447     Boolean weird = FALSE, reqFlag = FALSE;
3448
3449     fromX = fromY = toX = toY = -1;
3450     
3451     newGame = FALSE;
3452
3453     if (appData.debugMode)
3454       fprintf(debugFP, _("Parsing board: %s\n"), string);
3455
3456     move_str[0] = NULLCHAR;
3457     elapsed_time[0] = NULLCHAR;
3458     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3459         int  i = 0, j;
3460         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3461             if(string[i] == ' ') { ranks++; files = 0; }
3462             else files++;
3463             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3464             i++;
3465         }
3466         for(j = 0; j <i; j++) board_chars[j] = string[j];
3467         board_chars[i] = '\0';
3468         string += i + 1;
3469     }
3470     n = sscanf(string, PATTERN, &to_play, &double_push,
3471                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3472                &gamenum, white, black, &relation, &basetime, &increment,
3473                &white_stren, &black_stren, &white_time, &black_time,
3474                &moveNum, str, elapsed_time, move_str, &ics_flip,
3475                &ticking);
3476
3477     if (n < 21) {
3478         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3479         DisplayError(str, 0);
3480         return;
3481     }
3482
3483     /* Convert the move number to internal form */
3484     moveNum = (moveNum - 1) * 2;
3485     if (to_play == 'B') moveNum++;
3486     if (moveNum >= MAX_MOVES) {
3487       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3488                         0, 1);
3489       return;
3490     }
3491     
3492     switch (relation) {
3493       case RELATION_OBSERVING_PLAYED:
3494       case RELATION_OBSERVING_STATIC:
3495         if (gamenum == -1) {
3496             /* Old ICC buglet */
3497             relation = RELATION_OBSERVING_STATIC;
3498         }
3499         newGameMode = IcsObserving;
3500         break;
3501       case RELATION_PLAYING_MYMOVE:
3502       case RELATION_PLAYING_NOTMYMOVE:
3503         newGameMode =
3504           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3505             IcsPlayingWhite : IcsPlayingBlack;
3506         break;
3507       case RELATION_EXAMINING:
3508         newGameMode = IcsExamining;
3509         break;
3510       case RELATION_ISOLATED_BOARD:
3511       default:
3512         /* Just display this board.  If user was doing something else,
3513            we will forget about it until the next board comes. */ 
3514         newGameMode = IcsIdle;
3515         break;
3516       case RELATION_STARTING_POSITION:
3517         newGameMode = gameMode;
3518         break;
3519     }
3520     
3521     /* Modify behavior for initial board display on move listing
3522        of wild games.
3523        */
3524     switch (ics_getting_history) {
3525       case H_FALSE:
3526       case H_REQUESTED:
3527         break;
3528       case H_GOT_REQ_HEADER:
3529       case H_GOT_UNREQ_HEADER:
3530         /* This is the initial position of the current game */
3531         gamenum = ics_gamenum;
3532         moveNum = 0;            /* old ICS bug workaround */
3533         if (to_play == 'B') {
3534           startedFromSetupPosition = TRUE;
3535           blackPlaysFirst = TRUE;
3536           moveNum = 1;
3537           if (forwardMostMove == 0) forwardMostMove = 1;
3538           if (backwardMostMove == 0) backwardMostMove = 1;
3539           if (currentMove == 0) currentMove = 1;
3540         }
3541         newGameMode = gameMode;
3542         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3543         break;
3544       case H_GOT_UNWANTED_HEADER:
3545         /* This is an initial board that we don't want */
3546         return;
3547       case H_GETTING_MOVES:
3548         /* Should not happen */
3549         DisplayError(_("Error gathering move list: extra board"), 0);
3550         ics_getting_history = H_FALSE;
3551         return;
3552     }
3553
3554    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3555                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3556      /* [HGM] We seem to have switched variant unexpectedly
3557       * Try to guess new variant from board size
3558       */
3559           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3560           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3561           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3562           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3563           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3564           if(!weird) newVariant = VariantNormal;
3565           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3566           /* Get a move list just to see the header, which
3567              will tell us whether this is really bug or zh */
3568           if (ics_getting_history == H_FALSE) {
3569             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3570             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3571             SendToICS(str);
3572           }
3573     }
3574     
3575     /* Take action if this is the first board of a new game, or of a
3576        different game than is currently being displayed.  */
3577     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3578         relation == RELATION_ISOLATED_BOARD) {
3579         
3580         /* Forget the old game and get the history (if any) of the new one */
3581         if (gameMode != BeginningOfGame) {
3582           Reset(TRUE, TRUE);
3583         }
3584         newGame = TRUE;
3585         if (appData.autoRaiseBoard) BoardToTop();
3586         prevMove = -3;
3587         if (gamenum == -1) {
3588             newGameMode = IcsIdle;
3589         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3590                    appData.getMoveList && !reqFlag) {
3591             /* Need to get game history */
3592             ics_getting_history = H_REQUESTED;
3593             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3594             SendToICS(str);
3595         }
3596         
3597         /* Initially flip the board to have black on the bottom if playing
3598            black or if the ICS flip flag is set, but let the user change
3599            it with the Flip View button. */
3600         flipView = appData.autoFlipView ? 
3601           (newGameMode == IcsPlayingBlack) || ics_flip :
3602           appData.flipView;
3603         
3604         /* Done with values from previous mode; copy in new ones */
3605         gameMode = newGameMode;
3606         ModeHighlight();
3607         ics_gamenum = gamenum;
3608         if (gamenum == gs_gamenum) {
3609             int klen = strlen(gs_kind);
3610             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3611             sprintf(str, "ICS %s", gs_kind);
3612             gameInfo.event = StrSave(str);
3613         } else {
3614             gameInfo.event = StrSave("ICS game");
3615         }
3616         gameInfo.site = StrSave(appData.icsHost);
3617         gameInfo.date = PGNDate();
3618         gameInfo.round = StrSave("-");
3619         gameInfo.white = StrSave(white);
3620         gameInfo.black = StrSave(black);
3621         timeControl = basetime * 60 * 1000;
3622         timeControl_2 = 0;
3623         timeIncrement = increment * 1000;
3624         movesPerSession = 0;
3625         gameInfo.timeControl = TimeControlTagValue();
3626         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3627   if (appData.debugMode) {
3628     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3629     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3630     setbuf(debugFP, NULL);
3631   }
3632
3633         gameInfo.outOfBook = NULL;
3634         
3635         /* Do we have the ratings? */
3636         if (strcmp(player1Name, white) == 0 &&
3637             strcmp(player2Name, black) == 0) {
3638             if (appData.debugMode)
3639               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3640                       player1Rating, player2Rating);
3641             gameInfo.whiteRating = player1Rating;
3642             gameInfo.blackRating = player2Rating;
3643         } else if (strcmp(player2Name, white) == 0 &&
3644                    strcmp(player1Name, black) == 0) {
3645             if (appData.debugMode)
3646               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3647                       player2Rating, player1Rating);
3648             gameInfo.whiteRating = player2Rating;
3649             gameInfo.blackRating = player1Rating;
3650         }
3651         player1Name[0] = player2Name[0] = NULLCHAR;
3652
3653         /* Silence shouts if requested */
3654         if (appData.quietPlay &&
3655             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3656             SendToICS(ics_prefix);
3657             SendToICS("set shout 0\n");
3658         }
3659     }
3660     
3661     /* Deal with midgame name changes */
3662     if (!newGame) {
3663         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3664             if (gameInfo.white) free(gameInfo.white);
3665             gameInfo.white = StrSave(white);
3666         }
3667         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3668             if (gameInfo.black) free(gameInfo.black);
3669             gameInfo.black = StrSave(black);
3670         }
3671     }
3672     
3673     /* Throw away game result if anything actually changes in examine mode */
3674     if (gameMode == IcsExamining && !newGame) {
3675         gameInfo.result = GameUnfinished;
3676         if (gameInfo.resultDetails != NULL) {
3677             free(gameInfo.resultDetails);
3678             gameInfo.resultDetails = NULL;
3679         }
3680     }
3681     
3682     /* In pausing && IcsExamining mode, we ignore boards coming
3683        in if they are in a different variation than we are. */
3684     if (pauseExamInvalid) return;
3685     if (pausing && gameMode == IcsExamining) {
3686         if (moveNum <= pauseExamForwardMostMove) {
3687             pauseExamInvalid = TRUE;
3688             forwardMostMove = pauseExamForwardMostMove;
3689             return;
3690         }
3691     }
3692     
3693   if (appData.debugMode) {
3694     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3695   }
3696     /* Parse the board */
3697     for (k = 0; k < ranks; k++) {
3698       for (j = 0; j < files; j++)
3699         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3700       if(gameInfo.holdingsWidth > 1) {
3701            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3702            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3703       }
3704     }
3705     CopyBoard(boards[moveNum], board);
3706     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3707     if (moveNum == 0) {
3708         startedFromSetupPosition =
3709           !CompareBoards(board, initialPosition);
3710         if(startedFromSetupPosition)
3711             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3712     }
3713
3714     /* [HGM] Set castling rights. Take the outermost Rooks,
3715        to make it also work for FRC opening positions. Note that board12
3716        is really defective for later FRC positions, as it has no way to
3717        indicate which Rook can castle if they are on the same side of King.
3718        For the initial position we grant rights to the outermost Rooks,
3719        and remember thos rights, and we then copy them on positions
3720        later in an FRC game. This means WB might not recognize castlings with
3721        Rooks that have moved back to their original position as illegal,
3722        but in ICS mode that is not its job anyway.
3723     */
3724     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3725     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3726
3727         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3728             if(board[0][i] == WhiteRook) j = i;
3729         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3730         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3731             if(board[0][i] == WhiteRook) j = i;
3732         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3733         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3734             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3735         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3736         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3737             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3738         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3739
3740         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3741         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3742             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3743         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3744             if(board[BOARD_HEIGHT-1][k] == bKing)
3745                 initialRights[5] = castlingRights[moveNum][5] = k;
3746         if(gameInfo.variant == VariantTwoKings) {
3747             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3748             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3749             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3750         }
3751     } else { int r;
3752         r = castlingRights[moveNum][0] = initialRights[0];
3753         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3754         r = castlingRights[moveNum][1] = initialRights[1];
3755         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3756         r = castlingRights[moveNum][3] = initialRights[3];
3757         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3758         r = castlingRights[moveNum][4] = initialRights[4];
3759         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3760         /* wildcastle kludge: always assume King has rights */
3761         r = castlingRights[moveNum][2] = initialRights[2];
3762         r = castlingRights[moveNum][5] = initialRights[5];
3763     }
3764     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3765     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3766
3767     
3768     if (ics_getting_history == H_GOT_REQ_HEADER ||
3769         ics_getting_history == H_GOT_UNREQ_HEADER) {
3770         /* This was an initial position from a move list, not
3771            the current position */
3772         return;
3773     }
3774     
3775     /* Update currentMove and known move number limits */
3776     newMove = newGame || moveNum > forwardMostMove;
3777
3778     if (newGame) {
3779         forwardMostMove = backwardMostMove = currentMove = moveNum;
3780         if (gameMode == IcsExamining && moveNum == 0) {
3781           /* Workaround for ICS limitation: we are not told the wild
3782              type when starting to examine a game.  But if we ask for
3783              the move list, the move list header will tell us */
3784             ics_getting_history = H_REQUESTED;
3785             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3786             SendToICS(str);
3787         }
3788     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3789                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3790 #if ZIPPY
3791         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3792         /* [HGM] applied this also to an engine that is silently watching        */
3793         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3794             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3795             gameInfo.variant == currentlyInitializedVariant) {
3796           takeback = forwardMostMove - moveNum;
3797           for (i = 0; i < takeback; i++) {
3798             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3799             SendToProgram("undo\n", &first);
3800           }
3801         }
3802 #endif
3803
3804         forwardMostMove = moveNum;
3805         if (!pausing || currentMove > forwardMostMove)
3806           currentMove = forwardMostMove;
3807     } else {
3808         /* New part of history that is not contiguous with old part */ 
3809         if (pausing && gameMode == IcsExamining) {
3810             pauseExamInvalid = TRUE;
3811             forwardMostMove = pauseExamForwardMostMove;
3812             return;
3813         }
3814         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3815 #if ZIPPY
3816             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3817                 // [HGM] when we will receive the move list we now request, it will be
3818                 // fed to the engine from the first move on. So if the engine is not
3819                 // in the initial position now, bring it there.
3820                 InitChessProgram(&first, 0);
3821             }
3822 #endif
3823             ics_getting_history = H_REQUESTED;
3824             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3825             SendToICS(str);
3826         }
3827         forwardMostMove = backwardMostMove = currentMove = moveNum;
3828     }
3829     
3830     /* Update the clocks */
3831     if (strchr(elapsed_time, '.')) {
3832       /* Time is in ms */
3833       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3834       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3835     } else {
3836       /* Time is in seconds */
3837       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3838       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3839     }
3840       
3841
3842 #if ZIPPY
3843     if (appData.zippyPlay && newGame &&
3844         gameMode != IcsObserving && gameMode != IcsIdle &&
3845         gameMode != IcsExamining)
3846       ZippyFirstBoard(moveNum, basetime, increment);
3847 #endif
3848     
3849     /* Put the move on the move list, first converting
3850        to canonical algebraic form. */
3851     if (moveNum > 0) {
3852   if (appData.debugMode) {
3853     if (appData.debugMode) { int f = forwardMostMove;
3854         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3855                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3856     }
3857     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858     fprintf(debugFP, "moveNum = %d\n", moveNum);
3859     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860     setbuf(debugFP, NULL);
3861   }
3862         if (moveNum <= backwardMostMove) {
3863             /* We don't know what the board looked like before
3864                this move.  Punt. */
3865             strcpy(parseList[moveNum - 1], move_str);
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             moveList[moveNum - 1][0] = NULLCHAR;
3869         } else if (strcmp(move_str, "none") == 0) {
3870             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871             /* Again, we don't know what the board looked like;
3872                this is really the start of the game. */
3873             parseList[moveNum - 1][0] = NULLCHAR;
3874             moveList[moveNum - 1][0] = NULLCHAR;
3875             backwardMostMove = moveNum;
3876             startedFromSetupPosition = TRUE;
3877             fromX = fromY = toX = toY = -1;
3878         } else {
3879           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3880           //                 So we parse the long-algebraic move string in stead of the SAN move
3881           int valid; char buf[MSG_SIZ], *prom;
3882
3883           // str looks something like "Q/a1-a2"; kill the slash
3884           if(str[1] == '/') 
3885                 sprintf(buf, "%c%s", str[0], str+2);
3886           else  strcpy(buf, str); // might be castling
3887           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3888                 strcat(buf, prom); // long move lacks promo specification!
3889           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890                 if(appData.debugMode) 
3891                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892                 strcpy(move_str, buf);
3893           }
3894           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895                                 &fromX, &fromY, &toX, &toY, &promoChar)
3896                || ParseOneMove(buf, moveNum - 1, &moveType,
3897                                 &fromX, &fromY, &toX, &toY, &promoChar);
3898           // end of long SAN patch
3899           if (valid) {
3900             (void) CoordsToAlgebraic(boards[moveNum - 1],
3901                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3902                                      fromY, fromX, toY, toX, promoChar,
3903                                      parseList[moveNum-1]);
3904             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3905                              castlingRights[moveNum]) ) {
3906               case MT_NONE:
3907               case MT_STALEMATE:
3908               default:
3909                 break;
3910               case MT_CHECK:
3911                 if(gameInfo.variant != VariantShogi)
3912                     strcat(parseList[moveNum - 1], "+");
3913                 break;
3914               case MT_CHECKMATE:
3915               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3916                 strcat(parseList[moveNum - 1], "#");
3917                 break;
3918             }
3919             strcat(parseList[moveNum - 1], " ");
3920             strcat(parseList[moveNum - 1], elapsed_time);
3921             /* currentMoveString is set as a side-effect of ParseOneMove */
3922             strcpy(moveList[moveNum - 1], currentMoveString);
3923             strcat(moveList[moveNum - 1], "\n");
3924           } else {
3925             /* Move from ICS was illegal!?  Punt. */
3926   if (appData.debugMode) {
3927     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3928     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3929   }
3930             strcpy(parseList[moveNum - 1], move_str);
3931             strcat(parseList[moveNum - 1], " ");
3932             strcat(parseList[moveNum - 1], elapsed_time);
3933             moveList[moveNum - 1][0] = NULLCHAR;
3934             fromX = fromY = toX = toY = -1;
3935           }
3936         }
3937   if (appData.debugMode) {
3938     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3939     setbuf(debugFP, NULL);
3940   }
3941
3942 #if ZIPPY
3943         /* Send move to chess program (BEFORE animating it). */
3944         if (appData.zippyPlay && !newGame && newMove && 
3945            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3946
3947             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3948                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3949                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3950                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3951                             move_str);
3952                     DisplayError(str, 0);
3953                 } else {
3954                     if (first.sendTime) {
3955                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3956                     }
3957                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3958                     if (firstMove && !bookHit) {
3959                         firstMove = FALSE;
3960                         if (first.useColors) {
3961                           SendToProgram(gameMode == IcsPlayingWhite ?
3962                                         "white\ngo\n" :
3963                                         "black\ngo\n", &first);
3964                         } else {
3965                           SendToProgram("go\n", &first);
3966                         }
3967                         first.maybeThinking = TRUE;
3968                     }
3969                 }
3970             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3971               if (moveList[moveNum - 1][0] == NULLCHAR) {
3972                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3973                 DisplayError(str, 0);
3974               } else {
3975                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3976                 SendMoveToProgram(moveNum - 1, &first);
3977               }
3978             }
3979         }
3980 #endif
3981     }
3982
3983     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3984         /* If move comes from a remote source, animate it.  If it
3985            isn't remote, it will have already been animated. */
3986         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3987             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3988         }
3989         if (!pausing && appData.highlightLastMove) {
3990             SetHighlights(fromX, fromY, toX, toY);
3991         }
3992     }
3993     
3994     /* Start the clocks */
3995     whiteFlag = blackFlag = FALSE;
3996     appData.clockMode = !(basetime == 0 && increment == 0);
3997     if (ticking == 0) {
3998       ics_clock_paused = TRUE;
3999       StopClocks();
4000     } else if (ticking == 1) {
4001       ics_clock_paused = FALSE;
4002     }
4003     if (gameMode == IcsIdle ||
4004         relation == RELATION_OBSERVING_STATIC ||
4005         relation == RELATION_EXAMINING ||
4006         ics_clock_paused)
4007       DisplayBothClocks();
4008     else
4009       StartClocks();
4010     
4011     /* Display opponents and material strengths */
4012     if (gameInfo.variant != VariantBughouse &&
4013         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4014         if (tinyLayout || smallLayout) {
4015             if(gameInfo.variant == VariantNormal)
4016                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4017                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4018                     basetime, increment);
4019             else
4020                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4021                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4022                     basetime, increment, (int) gameInfo.variant);
4023         } else {
4024             if(gameInfo.variant == VariantNormal)
4025                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4026                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4027                     basetime, increment);
4028             else
4029                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4030                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4031                     basetime, increment, VariantName(gameInfo.variant));
4032         }
4033         DisplayTitle(str);
4034   if (appData.debugMode) {
4035     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4036   }
4037     }
4038
4039    
4040     /* Display the board */
4041     if (!pausing && !appData.noGUI) {
4042       
4043       if (appData.premove)
4044           if (!gotPremove || 
4045              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4046              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4047               ClearPremoveHighlights();
4048
4049       DrawPosition(FALSE, boards[currentMove]);
4050       DisplayMove(moveNum - 1);
4051       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4052             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4053               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4054         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4055       }
4056     }
4057
4058     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4059 #if ZIPPY
4060     if(bookHit) { // [HGM] book: simulate book reply
4061         static char bookMove[MSG_SIZ]; // a bit generous?
4062
4063         programStats.nodes = programStats.depth = programStats.time = 
4064         programStats.score = programStats.got_only_move = 0;
4065         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4066
4067         strcpy(bookMove, "move ");
4068         strcat(bookMove, bookHit);
4069         HandleMachineMove(bookMove, &first);
4070     }
4071 #endif
4072 }
4073
4074 void
4075 GetMoveListEvent()
4076 {
4077     char buf[MSG_SIZ];
4078     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4079         ics_getting_history = H_REQUESTED;
4080         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4081         SendToICS(buf);
4082     }
4083 }
4084
4085 void
4086 AnalysisPeriodicEvent(force)
4087      int force;
4088 {
4089     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4090          && !force) || !appData.periodicUpdates)
4091       return;
4092
4093     /* Send . command to Crafty to collect stats */
4094     SendToProgram(".\n", &first);
4095
4096     /* Don't send another until we get a response (this makes
4097        us stop sending to old Crafty's which don't understand
4098        the "." command (sending illegal cmds resets node count & time,
4099        which looks bad)) */
4100     programStats.ok_to_send = 0;
4101 }
4102
4103 void ics_update_width(new_width)
4104         int new_width;
4105 {
4106         ics_printf("set width %d\n", new_width);
4107 }
4108
4109 void
4110 SendMoveToProgram(moveNum, cps)
4111      int moveNum;
4112      ChessProgramState *cps;
4113 {
4114     char buf[MSG_SIZ];
4115
4116     if (cps->useUsermove) {
4117       SendToProgram("usermove ", cps);
4118     }
4119     if (cps->useSAN) {
4120       char *space;
4121       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4122         int len = space - parseList[moveNum];
4123         memcpy(buf, parseList[moveNum], len);
4124         buf[len++] = '\n';
4125         buf[len] = NULLCHAR;
4126       } else {
4127         sprintf(buf, "%s\n", parseList[moveNum]);
4128       }
4129       SendToProgram(buf, cps);
4130     } else {
4131       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4132         AlphaRank(moveList[moveNum], 4);
4133         SendToProgram(moveList[moveNum], cps);
4134         AlphaRank(moveList[moveNum], 4); // and back
4135       } else
4136       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4137        * the engine. It would be nice to have a better way to identify castle 
4138        * moves here. */
4139       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4140                                                                          && cps->useOOCastle) {
4141         int fromX = moveList[moveNum][0] - AAA; 
4142         int fromY = moveList[moveNum][1] - ONE;
4143         int toX = moveList[moveNum][2] - AAA; 
4144         int toY = moveList[moveNum][3] - ONE;
4145         if((boards[moveNum][fromY][fromX] == WhiteKing 
4146             && boards[moveNum][toY][toX] == WhiteRook)
4147            || (boards[moveNum][fromY][fromX] == BlackKing 
4148                && boards[moveNum][toY][toX] == BlackRook)) {
4149           if(toX > fromX) SendToProgram("O-O\n", cps);
4150           else SendToProgram("O-O-O\n", cps);
4151         }
4152         else SendToProgram(moveList[moveNum], cps);
4153       }
4154       else SendToProgram(moveList[moveNum], cps);
4155       /* End of additions by Tord */
4156     }
4157
4158     /* [HGM] setting up the opening has brought engine in force mode! */
4159     /*       Send 'go' if we are in a mode where machine should play. */
4160     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4161         (gameMode == TwoMachinesPlay   ||
4162 #ifdef ZIPPY
4163          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4164 #endif
4165          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4166         SendToProgram("go\n", cps);
4167   if (appData.debugMode) {
4168     fprintf(debugFP, "(extra)\n");
4169   }
4170     }
4171     setboardSpoiledMachineBlack = 0;
4172 }
4173
4174 void
4175 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4176      ChessMove moveType;
4177      int fromX, fromY, toX, toY;
4178 {
4179     char user_move[MSG_SIZ];
4180
4181     switch (moveType) {
4182       default:
4183         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4184                 (int)moveType, fromX, fromY, toX, toY);
4185         DisplayError(user_move + strlen("say "), 0);
4186         break;
4187       case WhiteKingSideCastle:
4188       case BlackKingSideCastle:
4189       case WhiteQueenSideCastleWild:
4190       case BlackQueenSideCastleWild:
4191       /* PUSH Fabien */
4192       case WhiteHSideCastleFR:
4193       case BlackHSideCastleFR:
4194       /* POP Fabien */
4195         sprintf(user_move, "o-o\n");
4196         break;
4197       case WhiteQueenSideCastle:
4198       case BlackQueenSideCastle:
4199       case WhiteKingSideCastleWild:
4200       case BlackKingSideCastleWild:
4201       /* PUSH Fabien */
4202       case WhiteASideCastleFR:
4203       case BlackASideCastleFR:
4204       /* POP Fabien */
4205         sprintf(user_move, "o-o-o\n");
4206         break;
4207       case WhitePromotionQueen:
4208       case BlackPromotionQueen:
4209       case WhitePromotionRook:
4210       case BlackPromotionRook:
4211       case WhitePromotionBishop:
4212       case BlackPromotionBishop:
4213       case WhitePromotionKnight:
4214       case BlackPromotionKnight:
4215       case WhitePromotionKing:
4216       case BlackPromotionKing:
4217       case WhitePromotionChancellor:
4218       case BlackPromotionChancellor:
4219       case WhitePromotionArchbishop:
4220       case BlackPromotionArchbishop:
4221         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4222             sprintf(user_move, "%c%c%c%c=%c\n",
4223                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4224                 PieceToChar(WhiteFerz));
4225         else if(gameInfo.variant == VariantGreat)
4226             sprintf(user_move, "%c%c%c%c=%c\n",
4227                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4228                 PieceToChar(WhiteMan));
4229         else
4230             sprintf(user_move, "%c%c%c%c=%c\n",
4231                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4232                 PieceToChar(PromoPiece(moveType)));
4233         break;
4234       case WhiteDrop:
4235       case BlackDrop:
4236         sprintf(user_move, "%c@%c%c\n",
4237                 ToUpper(PieceToChar((ChessSquare) fromX)),
4238                 AAA + toX, ONE + toY);
4239         break;
4240       case NormalMove:
4241       case WhiteCapturesEnPassant:
4242       case BlackCapturesEnPassant:
4243       case IllegalMove:  /* could be a variant we don't quite understand */
4244         sprintf(user_move, "%c%c%c%c\n",
4245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4246         break;
4247     }
4248     SendToICS(user_move);
4249     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4250         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4251 }
4252
4253 void
4254 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4255      int rf, ff, rt, ft;
4256      char promoChar;
4257      char move[7];
4258 {
4259     if (rf == DROP_RANK) {
4260         sprintf(move, "%c@%c%c\n",
4261                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4262     } else {
4263         if (promoChar == 'x' || promoChar == NULLCHAR) {
4264             sprintf(move, "%c%c%c%c\n",
4265                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4266         } else {
4267             sprintf(move, "%c%c%c%c%c\n",
4268                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4269         }
4270     }
4271 }
4272
4273 void
4274 ProcessICSInitScript(f)
4275      FILE *f;
4276 {
4277     char buf[MSG_SIZ];
4278
4279     while (fgets(buf, MSG_SIZ, f)) {
4280         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4281     }
4282
4283     fclose(f);
4284 }
4285
4286
4287 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4288 void
4289 AlphaRank(char *move, int n)
4290 {
4291 //    char *p = move, c; int x, y;
4292
4293     if (appData.debugMode) {
4294         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4295     }
4296
4297     if(move[1]=='*' && 
4298        move[2]>='0' && move[2]<='9' &&
4299        move[3]>='a' && move[3]<='x'    ) {
4300         move[1] = '@';
4301         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4302         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4303     } else
4304     if(move[0]>='0' && move[0]<='9' &&
4305        move[1]>='a' && move[1]<='x' &&
4306        move[2]>='0' && move[2]<='9' &&
4307        move[3]>='a' && move[3]<='x'    ) {
4308         /* input move, Shogi -> normal */
4309         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4310         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4311         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4312         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4313     } else
4314     if(move[1]=='@' &&
4315        move[3]>='0' && move[3]<='9' &&
4316        move[2]>='a' && move[2]<='x'    ) {
4317         move[1] = '*';
4318         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4319         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4320     } else
4321     if(
4322        move[0]>='a' && move[0]<='x' &&
4323        move[3]>='0' && move[3]<='9' &&
4324        move[2]>='a' && move[2]<='x'    ) {
4325          /* output move, normal -> Shogi */
4326         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4327         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4328         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4329         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4330         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4331     }
4332     if (appData.debugMode) {
4333         fprintf(debugFP, "   out = '%s'\n", move);
4334     }
4335 }
4336
4337 /* Parser for moves from gnuchess, ICS, or user typein box */
4338 Boolean
4339 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4340      char *move;
4341      int moveNum;
4342      ChessMove *moveType;
4343      int *fromX, *fromY, *toX, *toY;
4344      char *promoChar;
4345 {       
4346     if (appData.debugMode) {
4347         fprintf(debugFP, "move to parse: %s\n", move);
4348     }
4349     *moveType = yylexstr(moveNum, move);
4350
4351     switch (*moveType) {
4352       case WhitePromotionChancellor:
4353       case BlackPromotionChancellor:
4354       case WhitePromotionArchbishop:
4355       case BlackPromotionArchbishop:
4356       case WhitePromotionQueen:
4357       case BlackPromotionQueen:
4358       case WhitePromotionRook:
4359       case BlackPromotionRook:
4360       case WhitePromotionBishop:
4361       case BlackPromotionBishop:
4362       case WhitePromotionKnight:
4363       case BlackPromotionKnight:
4364       case WhitePromotionKing:
4365       case BlackPromotionKing:
4366       case NormalMove:
4367       case WhiteCapturesEnPassant:
4368       case BlackCapturesEnPassant:
4369       case WhiteKingSideCastle:
4370       case WhiteQueenSideCastle:
4371       case BlackKingSideCastle:
4372       case BlackQueenSideCastle:
4373       case WhiteKingSideCastleWild:
4374       case WhiteQueenSideCastleWild:
4375       case BlackKingSideCastleWild:
4376       case BlackQueenSideCastleWild:
4377       /* Code added by Tord: */
4378       case WhiteHSideCastleFR:
4379       case WhiteASideCastleFR:
4380       case BlackHSideCastleFR:
4381       case BlackASideCastleFR:
4382       /* End of code added by Tord */
4383       case IllegalMove:         /* bug or odd chess variant */
4384         *fromX = currentMoveString[0] - AAA;
4385         *fromY = currentMoveString[1] - ONE;
4386         *toX = currentMoveString[2] - AAA;
4387         *toY = currentMoveString[3] - ONE;
4388         *promoChar = currentMoveString[4];
4389         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4390             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4391     if (appData.debugMode) {
4392         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4393     }
4394             *fromX = *fromY = *toX = *toY = 0;
4395             return FALSE;
4396         }
4397         if (appData.testLegality) {
4398           return (*moveType != IllegalMove);
4399         } else {
4400           return !(*fromX == *toX && *fromY == *toY);
4401         }
4402
4403       case WhiteDrop:
4404       case BlackDrop:
4405         *fromX = *moveType == WhiteDrop ?
4406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407           (int) CharToPiece(ToLower(currentMoveString[0]));
4408         *fromY = DROP_RANK;
4409         *toX = currentMoveString[2] - AAA;
4410         *toY = currentMoveString[3] - ONE;
4411         *promoChar = NULLCHAR;
4412         return TRUE;
4413
4414       case AmbiguousMove:
4415       case ImpossibleMove:
4416       case (ChessMove) 0:       /* end of file */
4417       case ElapsedTime:
4418       case Comment:
4419       case PGNTag:
4420       case NAG:
4421       case WhiteWins:
4422       case BlackWins:
4423       case GameIsDrawn:
4424       default:
4425     if (appData.debugMode) {
4426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4427     }
4428         /* bug? */
4429         *fromX = *fromY = *toX = *toY = 0;
4430         *promoChar = NULLCHAR;
4431         return FALSE;
4432     }
4433 }
4434
4435 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4436 // All positions will have equal probability, but the current method will not provide a unique
4437 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4438 #define DARK 1
4439 #define LITE 2
4440 #define ANY 3
4441
4442 int squaresLeft[4];
4443 int piecesLeft[(int)BlackPawn];
4444 int seed, nrOfShuffles;
4445
4446 void GetPositionNumber()
4447 {       // sets global variable seed
4448         int i;
4449
4450         seed = appData.defaultFrcPosition;
4451         if(seed < 0) { // randomize based on time for negative FRC position numbers
4452                 for(i=0; i<50; i++) seed += random();
4453                 seed = random() ^ random() >> 8 ^ random() << 8;
4454                 if(seed<0) seed = -seed;
4455         }
4456 }
4457
4458 int put(Board board, int pieceType, int rank, int n, int shade)
4459 // put the piece on the (n-1)-th empty squares of the given shade
4460 {
4461         int i;
4462
4463         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4464                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4465                         board[rank][i] = (ChessSquare) pieceType;
4466                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4467                         squaresLeft[ANY]--;
4468                         piecesLeft[pieceType]--; 
4469                         return i;
4470                 }
4471         }
4472         return -1;
4473 }
4474
4475
4476 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4477 // calculate where the next piece goes, (any empty square), and put it there
4478 {
4479         int i;
4480
4481         i = seed % squaresLeft[shade];
4482         nrOfShuffles *= squaresLeft[shade];
4483         seed /= squaresLeft[shade];
4484         put(board, pieceType, rank, i, shade);
4485 }
4486
4487 void AddTwoPieces(Board board, int pieceType, int rank)
4488 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4489 {
4490         int i, n=squaresLeft[ANY], j=n-1, k;
4491
4492         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4493         i = seed % k;  // pick one
4494         nrOfShuffles *= k;
4495         seed /= k;
4496         while(i >= j) i -= j--;
4497         j = n - 1 - j; i += j;
4498         put(board, pieceType, rank, j, ANY);
4499         put(board, pieceType, rank, i, ANY);
4500 }
4501
4502 void SetUpShuffle(Board board, int number)
4503 {
4504         int i, p, first=1;
4505
4506         GetPositionNumber(); nrOfShuffles = 1;
4507
4508         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4509         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4510         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4511
4512         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4513
4514         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4515             p = (int) board[0][i];
4516             if(p < (int) BlackPawn) piecesLeft[p] ++;
4517             board[0][i] = EmptySquare;
4518         }
4519
4520         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4521             // shuffles restricted to allow normal castling put KRR first
4522             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4523                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4524             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4525                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4526             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4527                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4528             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4529                 put(board, WhiteRook, 0, 0, ANY);
4530             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4531         }
4532
4533         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4534             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4535             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4536                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4537                 while(piecesLeft[p] >= 2) {
4538                     AddOnePiece(board, p, 0, LITE);
4539                     AddOnePiece(board, p, 0, DARK);
4540                 }
4541                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4542             }
4543
4544         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4545             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4546             // but we leave King and Rooks for last, to possibly obey FRC restriction
4547             if(p == (int)WhiteRook) continue;
4548             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4549             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4550         }
4551
4552         // now everything is placed, except perhaps King (Unicorn) and Rooks
4553
4554         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4555             // Last King gets castling rights
4556             while(piecesLeft[(int)WhiteUnicorn]) {
4557                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4558                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4559             }
4560
4561             while(piecesLeft[(int)WhiteKing]) {
4562                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4563                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4564             }
4565
4566
4567         } else {
4568             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4569             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4570         }
4571
4572         // Only Rooks can be left; simply place them all
4573         while(piecesLeft[(int)WhiteRook]) {
4574                 i = put(board, WhiteRook, 0, 0, ANY);
4575                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4576                         if(first) {
4577                                 first=0;
4578                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4579                         }
4580                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4581                 }
4582         }
4583         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4584             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4585         }
4586
4587         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4588 }
4589
4590 int SetCharTable( char *table, const char * map )
4591 /* [HGM] moved here from winboard.c because of its general usefulness */
4592 /*       Basically a safe strcpy that uses the last character as King */
4593 {
4594     int result = FALSE; int NrPieces;
4595
4596     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4597                     && NrPieces >= 12 && !(NrPieces&1)) {
4598         int i; /* [HGM] Accept even length from 12 to 34 */
4599
4600         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4601         for( i=0; i<NrPieces/2-1; i++ ) {
4602             table[i] = map[i];
4603             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4604         }
4605         table[(int) WhiteKing]  = map[NrPieces/2-1];
4606         table[(int) BlackKing]  = map[NrPieces-1];
4607
4608         result = TRUE;
4609     }
4610
4611     return result;
4612 }
4613
4614 void Prelude(Board board)
4615 {       // [HGM] superchess: random selection of exo-pieces
4616         int i, j, k; ChessSquare p; 
4617         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4618
4619         GetPositionNumber(); // use FRC position number
4620
4621         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4622             SetCharTable(pieceToChar, appData.pieceToCharTable);
4623             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4624                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4625         }
4626
4627         j = seed%4;                 seed /= 4; 
4628         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4629         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4630         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4631         j = seed%3 + (seed%3 >= j); seed /= 3; 
4632         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4633         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4634         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4635         j = seed%3;                 seed /= 3; 
4636         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4637         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4638         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4639         j = seed%2 + (seed%2 >= j); seed /= 2; 
4640         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4641         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4642         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4643         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4644         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4645         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4646         put(board, exoPieces[0],    0, 0, ANY);
4647         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4648 }
4649
4650 void
4651 InitPosition(redraw)
4652      int redraw;
4653 {
4654     ChessSquare (* pieces)[BOARD_SIZE];
4655     int i, j, pawnRow, overrule,
4656     oldx = gameInfo.boardWidth,
4657     oldy = gameInfo.boardHeight,
4658     oldh = gameInfo.holdingsWidth,
4659     oldv = gameInfo.variant;
4660
4661     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4662
4663     /* [AS] Initialize pv info list [HGM] and game status */
4664     {
4665         for( i=0; i<MAX_MOVES; i++ ) {
4666             pvInfoList[i].depth = 0;
4667             epStatus[i]=EP_NONE;
4668             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4669         }
4670
4671         initialRulePlies = 0; /* 50-move counter start */
4672
4673         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4674         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4675     }
4676
4677     
4678     /* [HGM] logic here is completely changed. In stead of full positions */
4679     /* the initialized data only consist of the two backranks. The switch */
4680     /* selects which one we will use, which is than copied to the Board   */
4681     /* initialPosition, which for the rest is initialized by Pawns and    */
4682     /* empty squares. This initial position is then copied to boards[0],  */
4683     /* possibly after shuffling, so that it remains available.            */
4684
4685     gameInfo.holdingsWidth = 0; /* default board sizes */
4686     gameInfo.boardWidth    = 8;
4687     gameInfo.boardHeight   = 8;
4688     gameInfo.holdingsSize  = 0;
4689     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4690     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4691     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4692
4693     switch (gameInfo.variant) {
4694     case VariantFischeRandom:
4695       shuffleOpenings = TRUE;
4696     default:
4697       pieces = FIDEArray;
4698       break;
4699     case VariantShatranj:
4700       pieces = ShatranjArray;
4701       nrCastlingRights = 0;
4702       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4703       break;
4704     case VariantMakruk:
4705       pieces = makrukArray;
4706       nrCastlingRights = 0;
4707       startedFromSetupPosition = TRUE;
4708       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4709       break;
4710     case VariantTwoKings:
4711       pieces = twoKingsArray;
4712       break;
4713     case VariantCapaRandom:
4714       shuffleOpenings = TRUE;
4715     case VariantCapablanca:
4716       pieces = CapablancaArray;
4717       gameInfo.boardWidth = 10;
4718       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4719       break;
4720     case VariantGothic:
4721       pieces = GothicArray;
4722       gameInfo.boardWidth = 10;
4723       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4724       break;
4725     case VariantJanus:
4726       pieces = JanusArray;
4727       gameInfo.boardWidth = 10;
4728       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4729       nrCastlingRights = 6;
4730         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4731         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4732         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4733         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4734         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4735         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4736       break;
4737     case VariantFalcon:
4738       pieces = FalconArray;
4739       gameInfo.boardWidth = 10;
4740       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4741       break;
4742     case VariantXiangqi:
4743       pieces = XiangqiArray;
4744       gameInfo.boardWidth  = 9;
4745       gameInfo.boardHeight = 10;
4746       nrCastlingRights = 0;
4747       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4748       break;
4749     case VariantShogi:
4750       pieces = ShogiArray;
4751       gameInfo.boardWidth  = 9;
4752       gameInfo.boardHeight = 9;
4753       gameInfo.holdingsSize = 7;
4754       nrCastlingRights = 0;
4755       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4756       break;
4757     case VariantCourier:
4758       pieces = CourierArray;
4759       gameInfo.boardWidth  = 12;
4760       nrCastlingRights = 0;
4761       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4762       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4763       break;
4764     case VariantKnightmate:
4765       pieces = KnightmateArray;
4766       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4767       break;
4768     case VariantFairy:
4769       pieces = fairyArray;
4770       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4771       break;
4772     case VariantGreat:
4773       pieces = GreatArray;
4774       gameInfo.boardWidth = 10;
4775       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4776       gameInfo.holdingsSize = 8;
4777       break;
4778     case VariantSuper:
4779       pieces = FIDEArray;
4780       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4781       gameInfo.holdingsSize = 8;
4782       startedFromSetupPosition = TRUE;
4783       break;
4784     case VariantCrazyhouse:
4785     case VariantBughouse:
4786       pieces = FIDEArray;
4787       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4788       gameInfo.holdingsSize = 5;
4789       break;
4790     case VariantWildCastle:
4791       pieces = FIDEArray;
4792       /* !!?shuffle with kings guaranteed to be on d or e file */
4793       shuffleOpenings = 1;
4794       break;
4795     case VariantNoCastle:
4796       pieces = FIDEArray;
4797       nrCastlingRights = 0;
4798       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4799       /* !!?unconstrained back-rank shuffle */
4800       shuffleOpenings = 1;
4801       break;
4802     }
4803
4804     overrule = 0;
4805     if(appData.NrFiles >= 0) {
4806         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4807         gameInfo.boardWidth = appData.NrFiles;
4808     }
4809     if(appData.NrRanks >= 0) {
4810         gameInfo.boardHeight = appData.NrRanks;
4811     }
4812     if(appData.holdingsSize >= 0) {
4813         i = appData.holdingsSize;
4814         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4815         gameInfo.holdingsSize = i;
4816     }
4817     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4818     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4819         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4820
4821     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4822     if(pawnRow < 1) pawnRow = 1;
4823     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4824
4825     /* User pieceToChar list overrules defaults */
4826     if(appData.pieceToCharTable != NULL)
4827         SetCharTable(pieceToChar, appData.pieceToCharTable);
4828
4829     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4830
4831         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4832             s = (ChessSquare) 0; /* account holding counts in guard band */
4833         for( i=0; i<BOARD_HEIGHT; i++ )
4834             initialPosition[i][j] = s;
4835
4836         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4837         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4838         initialPosition[pawnRow][j] = WhitePawn;
4839         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4840         if(gameInfo.variant == VariantXiangqi) {
4841             if(j&1) {
4842                 initialPosition[pawnRow][j] = 
4843                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4844                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4845                    initialPosition[2][j] = WhiteCannon;
4846                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4847                 }
4848             }
4849         }
4850         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4851     }
4852     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4853
4854             j=BOARD_LEFT+1;
4855             initialPosition[1][j] = WhiteBishop;
4856             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4857             j=BOARD_RGHT-2;
4858             initialPosition[1][j] = WhiteRook;
4859             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4860     }
4861
4862     if( nrCastlingRights == -1) {
4863         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4864         /*       This sets default castling rights from none to normal corners   */
4865         /* Variants with other castling rights must set them themselves above    */
4866         nrCastlingRights = 6;
4867        
4868         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4869         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4870         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4871         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4872         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4873         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4874      }
4875
4876      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4877      if(gameInfo.variant == VariantGreat) { // promotion commoners
4878         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4879         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4880         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4881         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4882      }
4883   if (appData.debugMode) {
4884     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4885   }
4886     if(shuffleOpenings) {
4887         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4888         startedFromSetupPosition = TRUE;
4889     }
4890     if(startedFromPositionFile) {
4891       /* [HGM] loadPos: use PositionFile for every new game */
4892       CopyBoard(initialPosition, filePosition);
4893       for(i=0; i<nrCastlingRights; i++)
4894           castlingRights[0][i] = initialRights[i] = fileRights[i];
4895       startedFromSetupPosition = TRUE;
4896     }
4897
4898     CopyBoard(boards[0], initialPosition);
4899
4900     if(oldx != gameInfo.boardWidth ||
4901        oldy != gameInfo.boardHeight ||
4902        oldh != gameInfo.holdingsWidth
4903 #ifdef GOTHIC
4904        || oldv == VariantGothic ||        // For licensing popups
4905        gameInfo.variant == VariantGothic
4906 #endif
4907 #ifdef FALCON
4908        || oldv == VariantFalcon ||
4909        gameInfo.variant == VariantFalcon
4910 #endif
4911                                          )
4912             InitDrawingSizes(-2 ,0);
4913
4914     if (redraw)
4915       DrawPosition(TRUE, boards[currentMove]);
4916 }
4917
4918 void
4919 SendBoard(cps, moveNum)
4920      ChessProgramState *cps;
4921      int moveNum;
4922 {
4923     char message[MSG_SIZ];
4924     
4925     if (cps->useSetboard) {
4926       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4927       sprintf(message, "setboard %s\n", fen);
4928       SendToProgram(message, cps);
4929       free(fen);
4930
4931     } else {
4932       ChessSquare *bp;
4933       int i, j;
4934       /* Kludge to set black to move, avoiding the troublesome and now
4935        * deprecated "black" command.
4936        */
4937       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4938
4939       SendToProgram("edit\n", cps);
4940       SendToProgram("#\n", cps);
4941       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4942         bp = &boards[moveNum][i][BOARD_LEFT];
4943         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4944           if ((int) *bp < (int) BlackPawn) {
4945             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4946                     AAA + j, ONE + i);
4947             if(message[0] == '+' || message[0] == '~') {
4948                 sprintf(message, "%c%c%c+\n",
4949                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4950                         AAA + j, ONE + i);
4951             }
4952             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4953                 message[1] = BOARD_RGHT   - 1 - j + '1';
4954                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4955             }
4956             SendToProgram(message, cps);
4957           }
4958         }
4959       }
4960     
4961       SendToProgram("c\n", cps);
4962       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4963         bp = &boards[moveNum][i][BOARD_LEFT];
4964         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4965           if (((int) *bp != (int) EmptySquare)
4966               && ((int) *bp >= (int) BlackPawn)) {
4967             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4968                     AAA + j, ONE + i);
4969             if(message[0] == '+' || message[0] == '~') {
4970                 sprintf(message, "%c%c%c+\n",
4971                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4972                         AAA + j, ONE + i);
4973             }
4974             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4975                 message[1] = BOARD_RGHT   - 1 - j + '1';
4976                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4977             }
4978             SendToProgram(message, cps);
4979           }
4980         }
4981       }
4982     
4983       SendToProgram(".\n", cps);
4984     }
4985     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4986 }
4987
4988 int
4989 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4990 {
4991     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4992     /* [HGM] add Shogi promotions */
4993     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4994     ChessSquare piece;
4995     ChessMove moveType;
4996     Boolean premove;
4997
4998     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4999     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5000
5001     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5002       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5003         return FALSE;
5004
5005     piece = boards[currentMove][fromY][fromX];
5006     if(gameInfo.variant == VariantShogi) {
5007         promotionZoneSize = 3;
5008         highestPromotingPiece = (int)WhiteFerz;
5009     } else if(gameInfo.variant == VariantMakruk) {
5010         promotionZoneSize = 3;
5011     }
5012
5013     // next weed out all moves that do not touch the promotion zone at all
5014     if((int)piece >= BlackPawn) {
5015         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5016              return FALSE;
5017         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5018     } else {
5019         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5020            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5021     }
5022
5023     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5024
5025     // weed out mandatory Shogi promotions
5026     if(gameInfo.variant == VariantShogi) {
5027         if(piece >= BlackPawn) {
5028             if(toY == 0 && piece == BlackPawn ||
5029                toY == 0 && piece == BlackQueen ||
5030                toY <= 1 && piece == BlackKnight) {
5031                 *promoChoice = '+';
5032                 return FALSE;
5033             }
5034         } else {
5035             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5036                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5037                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5038                 *promoChoice = '+';
5039                 return FALSE;
5040             }
5041         }
5042     }
5043
5044     // weed out obviously illegal Pawn moves
5045     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5046         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5047         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5048         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5049         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5050         // note we are not allowed to test for valid (non-)capture, due to premove
5051     }
5052
5053     // we either have a choice what to promote to, or (in Shogi) whether to promote
5054     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5055         *promoChoice = PieceToChar(BlackFerz);  // no choice
5056         return FALSE;
5057     }
5058     if(appData.alwaysPromoteToQueen) { // predetermined
5059         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5060              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5061         else *promoChoice = PieceToChar(BlackQueen);
5062         return FALSE;
5063     }
5064
5065     // suppress promotion popup on illegal moves that are not premoves
5066     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5067               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5068     if(appData.testLegality && !premove) {
5069         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5070                         epStatus[currentMove], castlingRights[currentMove],
5071                         fromY, fromX, toY, toX, NULLCHAR);
5072         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5073            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5074             return FALSE;
5075     }
5076
5077     return TRUE;
5078 }
5079
5080 int
5081 InPalace(row, column)
5082      int row, column;
5083 {   /* [HGM] for Xiangqi */
5084     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5085          column < (BOARD_WIDTH + 4)/2 &&
5086          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5087     return FALSE;
5088 }
5089
5090 int
5091 PieceForSquare (x, y)
5092      int x;
5093      int y;
5094 {
5095   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5096      return -1;
5097   else
5098      return boards[currentMove][y][x];
5099 }
5100
5101 int
5102 OKToStartUserMove(x, y)
5103      int x, y;
5104 {
5105     ChessSquare from_piece;
5106     int white_piece;
5107
5108     if (matchMode) return FALSE;
5109     if (gameMode == EditPosition) return TRUE;
5110
5111     if (x >= 0 && y >= 0)
5112       from_piece = boards[currentMove][y][x];
5113     else
5114       from_piece = EmptySquare;
5115
5116     if (from_piece == EmptySquare) return FALSE;
5117
5118     white_piece = (int)from_piece >= (int)WhitePawn &&
5119       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5120
5121     switch (gameMode) {
5122       case PlayFromGameFile:
5123       case AnalyzeFile:
5124       case TwoMachinesPlay:
5125       case EndOfGame:
5126         return FALSE;
5127
5128       case IcsObserving:
5129       case IcsIdle:
5130         return FALSE;
5131
5132       case MachinePlaysWhite:
5133       case IcsPlayingBlack:
5134         if (appData.zippyPlay) return FALSE;
5135         if (white_piece) {
5136             DisplayMoveError(_("You are playing Black"));
5137             return FALSE;
5138         }
5139         break;
5140
5141       case MachinePlaysBlack:
5142       case IcsPlayingWhite:
5143         if (appData.zippyPlay) return FALSE;
5144         if (!white_piece) {
5145             DisplayMoveError(_("You are playing White"));
5146             return FALSE;
5147         }
5148         break;
5149
5150       case EditGame:
5151         if (!white_piece && WhiteOnMove(currentMove)) {
5152             DisplayMoveError(_("It is White's turn"));
5153             return FALSE;
5154         }           
5155         if (white_piece && !WhiteOnMove(currentMove)) {
5156             DisplayMoveError(_("It is Black's turn"));
5157             return FALSE;
5158         }           
5159         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5160             /* Editing correspondence game history */
5161             /* Could disallow this or prompt for confirmation */
5162             cmailOldMove = -1;
5163         }
5164         if (currentMove < forwardMostMove) {
5165             /* Discarding moves */
5166             /* Could prompt for confirmation here,
5167                but I don't think that's such a good idea */
5168             forwardMostMove = currentMove;
5169         }
5170         break;
5171
5172       case BeginningOfGame:
5173         if (appData.icsActive) return FALSE;
5174         if (!appData.noChessProgram) {
5175             if (!white_piece) {
5176                 DisplayMoveError(_("You are playing White"));
5177                 return FALSE;
5178             }
5179         }
5180         break;
5181         
5182       case Training:
5183         if (!white_piece && WhiteOnMove(currentMove)) {
5184             DisplayMoveError(_("It is White's turn"));
5185             return FALSE;
5186         }           
5187         if (white_piece && !WhiteOnMove(currentMove)) {
5188             DisplayMoveError(_("It is Black's turn"));
5189             return FALSE;
5190         }           
5191         break;
5192
5193       default:
5194       case IcsExamining:
5195         break;
5196     }
5197     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5198         && gameMode != AnalyzeFile && gameMode != Training) {
5199         DisplayMoveError(_("Displayed position is not current"));
5200         return FALSE;
5201     }
5202     return TRUE;
5203 }
5204
5205 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5206 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5207 int lastLoadGameUseList = FALSE;
5208 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5209 ChessMove lastLoadGameStart = (ChessMove) 0;
5210
5211 ChessMove
5212 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5213      int fromX, fromY, toX, toY;
5214      int promoChar;
5215      Boolean captureOwn;
5216 {
5217     ChessMove moveType;
5218     ChessSquare pdown, pup;
5219
5220     /* Check if the user is playing in turn.  This is complicated because we
5221        let the user "pick up" a piece before it is his turn.  So the piece he
5222        tried to pick up may have been captured by the time he puts it down!
5223        Therefore we use the color the user is supposed to be playing in this
5224        test, not the color of the piece that is currently on the starting
5225        square---except in EditGame mode, where the user is playing both
5226        sides; fortunately there the capture race can't happen.  (It can
5227        now happen in IcsExamining mode, but that's just too bad.  The user
5228        will get a somewhat confusing message in that case.)
5229        */
5230
5231     switch (gameMode) {
5232       case PlayFromGameFile:
5233       case AnalyzeFile:
5234       case TwoMachinesPlay:
5235       case EndOfGame:
5236       case IcsObserving:
5237       case IcsIdle:
5238         /* We switched into a game mode where moves are not accepted,
5239            perhaps while the mouse button was down. */
5240         return ImpossibleMove;
5241
5242       case MachinePlaysWhite:
5243         /* User is moving for Black */
5244         if (WhiteOnMove(currentMove)) {
5245             DisplayMoveError(_("It is White's turn"));
5246             return ImpossibleMove;
5247         }
5248         break;
5249
5250       case MachinePlaysBlack:
5251         /* User is moving for White */
5252         if (!WhiteOnMove(currentMove)) {
5253             DisplayMoveError(_("It is Black's turn"));
5254             return ImpossibleMove;
5255         }
5256         break;
5257
5258       case EditGame:
5259       case IcsExamining:
5260       case BeginningOfGame:
5261       case AnalyzeMode:
5262       case Training:
5263         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5264             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5265             /* User is moving for Black */
5266             if (WhiteOnMove(currentMove)) {
5267                 DisplayMoveError(_("It is White's turn"));
5268                 return ImpossibleMove;
5269             }
5270         } else {
5271             /* User is moving for White */
5272             if (!WhiteOnMove(currentMove)) {
5273                 DisplayMoveError(_("It is Black's turn"));
5274                 return ImpossibleMove;
5275             }
5276         }
5277         break;
5278
5279       case IcsPlayingBlack:
5280         /* User is moving for Black */
5281         if (WhiteOnMove(currentMove)) {
5282             if (!appData.premove) {
5283                 DisplayMoveError(_("It is White's turn"));
5284             } else if (toX >= 0 && toY >= 0) {
5285                 premoveToX = toX;
5286                 premoveToY = toY;
5287                 premoveFromX = fromX;
5288                 premoveFromY = fromY;
5289                 premovePromoChar = promoChar;
5290                 gotPremove = 1;
5291                 if (appData.debugMode) 
5292                     fprintf(debugFP, "Got premove: fromX %d,"
5293                             "fromY %d, toX %d, toY %d\n",
5294                             fromX, fromY, toX, toY);
5295             }
5296             return ImpossibleMove;
5297         }
5298         break;
5299
5300       case IcsPlayingWhite:
5301         /* User is moving for White */
5302         if (!WhiteOnMove(currentMove)) {
5303             if (!appData.premove) {
5304                 DisplayMoveError(_("It is Black's turn"));
5305             } else if (toX >= 0 && toY >= 0) {
5306                 premoveToX = toX;
5307                 premoveToY = toY;
5308                 premoveFromX = fromX;
5309                 premoveFromY = fromY;
5310                 premovePromoChar = promoChar;
5311                 gotPremove = 1;
5312                 if (appData.debugMode) 
5313                     fprintf(debugFP, "Got premove: fromX %d,"
5314                             "fromY %d, toX %d, toY %d\n",
5315                             fromX, fromY, toX, toY);
5316             }
5317             return ImpossibleMove;
5318         }
5319         break;
5320
5321       default:
5322         break;
5323
5324       case EditPosition:
5325         /* EditPosition, empty square, or different color piece;
5326            click-click move is possible */
5327         if (toX == -2 || toY == -2) {
5328             boards[0][fromY][fromX] = EmptySquare;
5329             return AmbiguousMove;
5330         } else if (toX >= 0 && toY >= 0) {
5331             boards[0][toY][toX] = boards[0][fromY][fromX];
5332             boards[0][fromY][fromX] = EmptySquare;
5333             return AmbiguousMove;
5334         }
5335         return ImpossibleMove;
5336     }
5337
5338     if(toX < 0 || toY < 0) return ImpossibleMove;
5339     pdown = boards[currentMove][fromY][fromX];
5340     pup = boards[currentMove][toY][toX];
5341
5342     /* [HGM] If move started in holdings, it means a drop */
5343     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5344          if( pup != EmptySquare ) return ImpossibleMove;
5345          if(appData.testLegality) {
5346              /* it would be more logical if LegalityTest() also figured out
5347               * which drops are legal. For now we forbid pawns on back rank.
5348               * Shogi is on its own here...
5349               */
5350              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5351                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5352                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5353          }
5354          return WhiteDrop; /* Not needed to specify white or black yet */
5355     }
5356
5357     userOfferedDraw = FALSE;
5358         
5359     /* [HGM] always test for legality, to get promotion info */
5360     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5361                           epStatus[currentMove], castlingRights[currentMove],
5362                                          fromY, fromX, toY, toX, promoChar);
5363     /* [HGM] but possibly ignore an IllegalMove result */
5364     if (appData.testLegality) {
5365         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5366             DisplayMoveError(_("Illegal move"));
5367             return ImpossibleMove;
5368         }
5369     }
5370
5371     return moveType;
5372     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5373        function is made into one that returns an OK move type if FinishMove
5374        should be called. This to give the calling driver routine the
5375        opportunity to finish the userMove input with a promotion popup,
5376        without bothering the user with this for invalid or illegal moves */
5377
5378 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5379 }
5380
5381 /* Common tail of UserMoveEvent and DropMenuEvent */
5382 int
5383 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5384      ChessMove moveType;
5385      int fromX, fromY, toX, toY;
5386      /*char*/int promoChar;
5387 {
5388     char *bookHit = 0;
5389
5390     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5391         // [HGM] superchess: suppress promotions to non-available piece
5392         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5393         if(WhiteOnMove(currentMove)) {
5394             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5395         } else {
5396             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5397         }
5398     }
5399
5400     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5401        move type in caller when we know the move is a legal promotion */
5402     if(moveType == NormalMove && promoChar)
5403         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5404
5405     /* [HGM] convert drag-and-drop piece drops to standard form */
5406     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5407          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5408            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5409                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5410            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5411            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5412            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5413            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5414          fromY = DROP_RANK;
5415     }
5416
5417     /* [HGM] <popupFix> The following if has been moved here from
5418        UserMoveEvent(). Because it seemed to belong here (why not allow
5419        piece drops in training games?), and because it can only be
5420        performed after it is known to what we promote. */
5421     if (gameMode == Training) {
5422       /* compare the move played on the board to the next move in the
5423        * game. If they match, display the move and the opponent's response. 
5424        * If they don't match, display an error message.
5425        */
5426       int saveAnimate;
5427       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5428       CopyBoard(testBoard, boards[currentMove]);
5429       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5430
5431       if (CompareBoards(testBoard, boards[currentMove+1])) {
5432         ForwardInner(currentMove+1);
5433
5434         /* Autoplay the opponent's response.
5435          * if appData.animate was TRUE when Training mode was entered,
5436          * the response will be animated.
5437          */
5438         saveAnimate = appData.animate;
5439         appData.animate = animateTraining;
5440         ForwardInner(currentMove+1);
5441         appData.animate = saveAnimate;
5442
5443         /* check for the end of the game */
5444         if (currentMove >= forwardMostMove) {
5445           gameMode = PlayFromGameFile;
5446           ModeHighlight();
5447           SetTrainingModeOff();
5448           DisplayInformation(_("End of game"));
5449         }
5450       } else {
5451         DisplayError(_("Incorrect move"), 0);
5452       }
5453       return 1;
5454     }
5455
5456   /* Ok, now we know that the move is good, so we can kill
5457      the previous line in Analysis Mode */
5458   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5459     forwardMostMove = currentMove;
5460   }
5461
5462   /* If we need the chess program but it's dead, restart it */
5463   ResurrectChessProgram();
5464
5465   /* A user move restarts a paused game*/
5466   if (pausing)
5467     PauseEvent();
5468
5469   thinkOutput[0] = NULLCHAR;
5470
5471   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5472
5473   if (gameMode == BeginningOfGame) {
5474     if (appData.noChessProgram) {
5475       gameMode = EditGame;
5476       SetGameInfo();
5477     } else {
5478       char buf[MSG_SIZ];
5479       gameMode = MachinePlaysBlack;
5480       StartClocks();
5481       SetGameInfo();
5482       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5483       DisplayTitle(buf);
5484       if (first.sendName) {
5485         sprintf(buf, "name %s\n", gameInfo.white);
5486         SendToProgram(buf, &first);
5487       }
5488       StartClocks();
5489     }
5490     ModeHighlight();
5491   }
5492
5493   /* Relay move to ICS or chess engine */
5494   if (appData.icsActive) {
5495     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5496         gameMode == IcsExamining) {
5497       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5498       ics_user_moved = 1;
5499     }
5500   } else {
5501     if (first.sendTime && (gameMode == BeginningOfGame ||
5502                            gameMode == MachinePlaysWhite ||
5503                            gameMode == MachinePlaysBlack)) {
5504       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5505     }
5506     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5507          // [HGM] book: if program might be playing, let it use book
5508         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5509         first.maybeThinking = TRUE;
5510     } else SendMoveToProgram(forwardMostMove-1, &first);
5511     if (currentMove == cmailOldMove + 1) {
5512       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5513     }
5514   }
5515
5516   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5517
5518   switch (gameMode) {
5519   case EditGame:
5520     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5521                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5522     case MT_NONE:
5523     case MT_CHECK:
5524       break;
5525     case MT_CHECKMATE:
5526     case MT_STAINMATE:
5527       if (WhiteOnMove(currentMove)) {
5528         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5529       } else {
5530         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5531       }
5532       break;
5533     case MT_STALEMATE:
5534       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5535       break;
5536     }
5537     break;
5538     
5539   case MachinePlaysBlack:
5540   case MachinePlaysWhite:
5541     /* disable certain menu options while machine is thinking */
5542     SetMachineThinkingEnables();
5543     break;
5544
5545   default:
5546     break;
5547   }
5548
5549   if(bookHit) { // [HGM] book: simulate book reply
5550         static char bookMove[MSG_SIZ]; // a bit generous?
5551
5552         programStats.nodes = programStats.depth = programStats.time = 
5553         programStats.score = programStats.got_only_move = 0;
5554         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5555
5556         strcpy(bookMove, "move ");
5557         strcat(bookMove, bookHit);
5558         HandleMachineMove(bookMove, &first);
5559   }
5560   return 1;
5561 }
5562
5563 void
5564 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5565      int fromX, fromY, toX, toY;
5566      int promoChar;
5567 {
5568     /* [HGM] This routine was added to allow calling of its two logical
5569        parts from other modules in the old way. Before, UserMoveEvent()
5570        automatically called FinishMove() if the move was OK, and returned
5571        otherwise. I separated the two, in order to make it possible to
5572        slip a promotion popup in between. But that it always needs two
5573        calls, to the first part, (now called UserMoveTest() ), and to
5574        FinishMove if the first part succeeded. Calls that do not need
5575        to do anything in between, can call this routine the old way. 
5576     */
5577     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5578 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5579     if(moveType == AmbiguousMove)
5580         DrawPosition(FALSE, boards[currentMove]);
5581     else if(moveType != ImpossibleMove && moveType != Comment)
5582         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5583 }
5584
5585 void LeftClick(ClickType clickType, int xPix, int yPix)
5586 {
5587     int x, y;
5588     Boolean saveAnimate;
5589     static int second = 0, promotionChoice = 0;
5590     char promoChoice = NULLCHAR;
5591
5592     if (clickType == Press) ErrorPopDown();
5593
5594     x = EventToSquare(xPix, BOARD_WIDTH);
5595     y = EventToSquare(yPix, BOARD_HEIGHT);
5596     if (!flipView && y >= 0) {
5597         y = BOARD_HEIGHT - 1 - y;
5598     }
5599     if (flipView && x >= 0) {
5600         x = BOARD_WIDTH - 1 - x;
5601     }
5602
5603     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5604         if(clickType == Release) return; // ignore upclick of click-click destination
5605         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5606         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5607         if(gameInfo.holdingsWidth && 
5608                 (WhiteOnMove(currentMove) 
5609                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5610                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5611             // click in right holdings, for determining promotion piece
5612             ChessSquare p = boards[currentMove][y][x];
5613             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5614             if(p != EmptySquare) {
5615                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5616                 fromX = fromY = -1;
5617                 return;
5618             }
5619         }
5620         DrawPosition(FALSE, boards[currentMove]);
5621         return;
5622     }
5623
5624     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5625     if(clickType == Press
5626             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5627               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5628               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5629         return;
5630
5631     if (fromX == -1) {
5632         if (clickType == Press) {
5633             /* First square */
5634             if (OKToStartUserMove(x, y)) {
5635                 fromX = x;
5636                 fromY = y;
5637                 second = 0;
5638                 DragPieceBegin(xPix, yPix);
5639                 if (appData.highlightDragging) {
5640                     SetHighlights(x, y, -1, -1);
5641                 }
5642             }
5643         }
5644         return;
5645     }
5646
5647     /* fromX != -1 */
5648     if (clickType == Press && gameMode != EditPosition) {
5649         ChessSquare fromP;
5650         ChessSquare toP;
5651         int frc;
5652
5653         // ignore off-board to clicks
5654         if(y < 0 || x < 0) return;
5655
5656         /* Check if clicking again on the same color piece */
5657         fromP = boards[currentMove][fromY][fromX];
5658         toP = boards[currentMove][y][x];
5659         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5660         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5661              WhitePawn <= toP && toP <= WhiteKing &&
5662              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5663              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5664             (BlackPawn <= fromP && fromP <= BlackKing && 
5665              BlackPawn <= toP && toP <= BlackKing &&
5666              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5667              !(fromP == BlackKing && toP == BlackRook && frc))) {
5668             /* Clicked again on same color piece -- changed his mind */
5669             second = (x == fromX && y == fromY);
5670             if (appData.highlightDragging) {
5671                 SetHighlights(x, y, -1, -1);
5672             } else {
5673                 ClearHighlights();
5674             }
5675             if (OKToStartUserMove(x, y)) {
5676                 fromX = x;
5677                 fromY = y;
5678                 DragPieceBegin(xPix, yPix);
5679             }
5680             return;
5681         }
5682         // ignore clicks on holdings
5683         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5684     }
5685
5686     if (clickType == Release && x == fromX && y == fromY) {
5687         DragPieceEnd(xPix, yPix);
5688         if (appData.animateDragging) {
5689             /* Undo animation damage if any */
5690             DrawPosition(FALSE, NULL);
5691         }
5692         if (second) {
5693             /* Second up/down in same square; just abort move */
5694             second = 0;
5695             fromX = fromY = -1;
5696             ClearHighlights();
5697             gotPremove = 0;
5698             ClearPremoveHighlights();
5699         } else {
5700             /* First upclick in same square; start click-click mode */
5701             SetHighlights(x, y, -1, -1);
5702         }
5703         return;
5704     }
5705
5706     /* we now have a different from- and (possibly off-board) to-square */
5707     /* Completed move */
5708     toX = x;
5709     toY = y;
5710     saveAnimate = appData.animate;
5711     if (clickType == Press) {
5712         /* Finish clickclick move */
5713         if (appData.animate || appData.highlightLastMove) {
5714             SetHighlights(fromX, fromY, toX, toY);
5715         } else {
5716             ClearHighlights();
5717         }
5718     } else {
5719         /* Finish drag move */
5720         if (appData.highlightLastMove) {
5721             SetHighlights(fromX, fromY, toX, toY);
5722         } else {
5723             ClearHighlights();
5724         }
5725         DragPieceEnd(xPix, yPix);
5726         /* Don't animate move and drag both */
5727         appData.animate = FALSE;
5728     }
5729
5730     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5731     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5732         ClearHighlights();
5733         fromX = fromY = -1;
5734         DrawPosition(TRUE, NULL);
5735         return;
5736     }
5737
5738     // off-board moves should not be highlighted
5739     if(x < 0 || x < 0) ClearHighlights();
5740
5741     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5742         SetHighlights(fromX, fromY, toX, toY);
5743         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5744             // [HGM] super: promotion to captured piece selected from holdings
5745             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5746             promotionChoice = TRUE;
5747             // kludge follows to temporarily execute move on display, without promoting yet
5748             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5749             boards[currentMove][toY][toX] = p;
5750             DrawPosition(FALSE, boards[currentMove]);
5751             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5752             boards[currentMove][toY][toX] = q;
5753             DisplayMessage("Click in holdings to choose piece", "");
5754             return;
5755         }
5756         PromotionPopUp();
5757     } else {
5758         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5759         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5760         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5761         fromX = fromY = -1;
5762     }
5763     appData.animate = saveAnimate;
5764     if (appData.animate || appData.animateDragging) {
5765         /* Undo animation damage if needed */
5766         DrawPosition(FALSE, NULL);
5767     }
5768 }
5769
5770 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5771 {
5772 //    char * hint = lastHint;
5773     FrontEndProgramStats stats;
5774
5775     stats.which = cps == &first ? 0 : 1;
5776     stats.depth = cpstats->depth;
5777     stats.nodes = cpstats->nodes;
5778     stats.score = cpstats->score;
5779     stats.time = cpstats->time;
5780     stats.pv = cpstats->movelist;
5781     stats.hint = lastHint;
5782     stats.an_move_index = 0;
5783     stats.an_move_count = 0;
5784
5785     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5786         stats.hint = cpstats->move_name;
5787         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5788         stats.an_move_count = cpstats->nr_moves;
5789     }
5790
5791     SetProgramStats( &stats );
5792 }
5793
5794 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5795 {   // [HGM] book: this routine intercepts moves to simulate book replies
5796     char *bookHit = NULL;
5797
5798     //first determine if the incoming move brings opponent into his book
5799     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5800         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5801     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5802     if(bookHit != NULL && !cps->bookSuspend) {
5803         // make sure opponent is not going to reply after receiving move to book position
5804         SendToProgram("force\n", cps);
5805         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5806     }
5807     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5808     // now arrange restart after book miss
5809     if(bookHit) {
5810         // after a book hit we never send 'go', and the code after the call to this routine
5811         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5812         char buf[MSG_SIZ];
5813         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5814         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5815         SendToProgram(buf, cps);
5816         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5817     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5818         SendToProgram("go\n", cps);
5819         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5820     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5821         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5822             SendToProgram("go\n", cps); 
5823         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5824     }
5825     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5826 }
5827
5828 char *savedMessage;
5829 ChessProgramState *savedState;
5830 void DeferredBookMove(void)
5831 {
5832         if(savedState->lastPing != savedState->lastPong)
5833                     ScheduleDelayedEvent(DeferredBookMove, 10);
5834         else
5835         HandleMachineMove(savedMessage, savedState);
5836 }
5837
5838 void
5839 HandleMachineMove(message, cps)
5840      char *message;
5841      ChessProgramState *cps;
5842 {
5843     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5844     char realname[MSG_SIZ];
5845     int fromX, fromY, toX, toY;
5846     ChessMove moveType;
5847     char promoChar;
5848     char *p;
5849     int machineWhite;
5850     char *bookHit;
5851
5852 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5853     /*
5854      * Kludge to ignore BEL characters
5855      */
5856     while (*message == '\007') message++;
5857
5858     /*
5859      * [HGM] engine debug message: ignore lines starting with '#' character
5860      */
5861     if(cps->debug && *message == '#') return;
5862
5863     /*
5864      * Look for book output
5865      */
5866     if (cps == &first && bookRequested) {
5867         if (message[0] == '\t' || message[0] == ' ') {
5868             /* Part of the book output is here; append it */
5869             strcat(bookOutput, message);
5870             strcat(bookOutput, "  \n");
5871             return;
5872         } else if (bookOutput[0] != NULLCHAR) {
5873             /* All of book output has arrived; display it */
5874             char *p = bookOutput;
5875             while (*p != NULLCHAR) {
5876                 if (*p == '\t') *p = ' ';
5877                 p++;
5878             }
5879             DisplayInformation(bookOutput);
5880             bookRequested = FALSE;
5881             /* Fall through to parse the current output */
5882         }
5883     }
5884
5885     /*
5886      * Look for machine move.
5887      */
5888     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5889         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5890     {
5891         /* This method is only useful on engines that support ping */
5892         if (cps->lastPing != cps->lastPong) {
5893           if (gameMode == BeginningOfGame) {
5894             /* Extra move from before last new; ignore */
5895             if (appData.debugMode) {
5896                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5897             }
5898           } else {
5899             if (appData.debugMode) {
5900                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5901                         cps->which, gameMode);
5902             }
5903
5904             SendToProgram("undo\n", cps);
5905           }
5906           return;
5907         }
5908
5909         switch (gameMode) {
5910           case BeginningOfGame:
5911             /* Extra move from before last reset; ignore */
5912             if (appData.debugMode) {
5913                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5914             }
5915             return;
5916
5917           case EndOfGame:
5918           case IcsIdle:
5919           default:
5920             /* Extra move after we tried to stop.  The mode test is
5921                not a reliable way of detecting this problem, but it's
5922                the best we can do on engines that don't support ping.
5923             */
5924             if (appData.debugMode) {
5925                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5926                         cps->which, gameMode);
5927             }
5928             SendToProgram("undo\n", cps);
5929             return;
5930
5931           case MachinePlaysWhite:
5932           case IcsPlayingWhite:
5933             machineWhite = TRUE;
5934             break;
5935
5936           case MachinePlaysBlack:
5937           case IcsPlayingBlack:
5938             machineWhite = FALSE;
5939             break;
5940
5941           case TwoMachinesPlay:
5942             machineWhite = (cps->twoMachinesColor[0] == 'w');
5943             break;
5944         }
5945         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5946             if (appData.debugMode) {
5947                 fprintf(debugFP,
5948                         "Ignoring move out of turn by %s, gameMode %d"
5949                         ", forwardMost %d\n",
5950                         cps->which, gameMode, forwardMostMove);
5951             }
5952             return;
5953         }
5954
5955     if (appData.debugMode) { int f = forwardMostMove;
5956         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5957                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5958     }
5959         if(cps->alphaRank) AlphaRank(machineMove, 4);
5960         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5961                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5962             /* Machine move could not be parsed; ignore it. */
5963             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5964                     machineMove, cps->which);
5965             DisplayError(buf1, 0);
5966             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5967                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5968             if (gameMode == TwoMachinesPlay) {
5969               GameEnds(machineWhite ? BlackWins : WhiteWins,
5970                        buf1, GE_XBOARD);
5971             }
5972             return;
5973         }
5974
5975         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5976         /* So we have to redo legality test with true e.p. status here,  */
5977         /* to make sure an illegal e.p. capture does not slip through,   */
5978         /* to cause a forfeit on a justified illegal-move complaint      */
5979         /* of the opponent.                                              */
5980         if( gameMode==TwoMachinesPlay && appData.testLegality
5981             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5982                                                               ) {
5983            ChessMove moveType;
5984            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5985                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5986                              fromY, fromX, toY, toX, promoChar);
5987             if (appData.debugMode) {
5988                 int i;
5989                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5990                     castlingRights[forwardMostMove][i], castlingRank[i]);
5991                 fprintf(debugFP, "castling rights\n");
5992             }
5993             if(moveType == IllegalMove) {
5994                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5995                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5996                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5997                            buf1, GE_XBOARD);
5998                 return;
5999            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6000            /* [HGM] Kludge to handle engines that send FRC-style castling
6001               when they shouldn't (like TSCP-Gothic) */
6002            switch(moveType) {
6003              case WhiteASideCastleFR:
6004              case BlackASideCastleFR:
6005                toX+=2;
6006                currentMoveString[2]++;
6007                break;
6008              case WhiteHSideCastleFR:
6009              case BlackHSideCastleFR:
6010                toX--;
6011                currentMoveString[2]--;
6012                break;
6013              default: ; // nothing to do, but suppresses warning of pedantic compilers
6014            }
6015         }
6016         hintRequested = FALSE;
6017         lastHint[0] = NULLCHAR;
6018         bookRequested = FALSE;
6019         /* Program may be pondering now */
6020         cps->maybeThinking = TRUE;
6021         if (cps->sendTime == 2) cps->sendTime = 1;
6022         if (cps->offeredDraw) cps->offeredDraw--;
6023
6024 #if ZIPPY
6025         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6026             first.initDone) {
6027           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6028           ics_user_moved = 1;
6029           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6030                 char buf[3*MSG_SIZ];
6031
6032                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6033                         programStats.score / 100.,
6034                         programStats.depth,
6035                         programStats.time / 100.,
6036                         (unsigned int)programStats.nodes,
6037                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6038                         programStats.movelist);
6039                 SendToICS(buf);
6040 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6041           }
6042         }
6043 #endif
6044         /* currentMoveString is set as a side-effect of ParseOneMove */
6045         strcpy(machineMove, currentMoveString);
6046         strcat(machineMove, "\n");
6047         strcpy(moveList[forwardMostMove], machineMove);
6048
6049         /* [AS] Save move info and clear stats for next move */
6050         pvInfoList[ forwardMostMove ].score = programStats.score;
6051         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6052         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6053         ClearProgramStats();
6054         thinkOutput[0] = NULLCHAR;
6055         hiddenThinkOutputState = 0;
6056
6057         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6058
6059         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6060         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6061             int count = 0;
6062
6063             while( count < adjudicateLossPlies ) {
6064                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6065
6066                 if( count & 1 ) {
6067                     score = -score; /* Flip score for winning side */
6068                 }
6069
6070                 if( score > adjudicateLossThreshold ) {
6071                     break;
6072                 }
6073
6074                 count++;
6075             }
6076
6077             if( count >= adjudicateLossPlies ) {
6078                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6079
6080                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6081                     "Xboard adjudication", 
6082                     GE_XBOARD );
6083
6084                 return;
6085             }
6086         }
6087
6088         if( gameMode == TwoMachinesPlay ) {
6089           // [HGM] some adjudications useful with buggy engines
6090             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6091           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6092
6093
6094             if( appData.testLegality )
6095             {   /* [HGM] Some more adjudications for obstinate engines */
6096                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6097                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6098                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6099                 static int moveCount = 6;
6100                 ChessMove result;
6101                 char *reason = NULL;
6102
6103                 /* Count what is on board. */
6104                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6105                 {   ChessSquare p = boards[forwardMostMove][i][j];
6106                     int m=i;
6107
6108                     switch((int) p)
6109                     {   /* count B,N,R and other of each side */
6110                         case WhiteKing:
6111                         case BlackKing:
6112                              NrK++; break; // [HGM] atomic: count Kings
6113                         case WhiteKnight:
6114                              NrWN++; break;
6115                         case WhiteBishop:
6116                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6117                              bishopsColor |= 1 << ((i^j)&1);
6118                              NrWB++; break;
6119                         case BlackKnight:
6120                              NrBN++; break;
6121                         case BlackBishop:
6122                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6123                              bishopsColor |= 1 << ((i^j)&1);
6124                              NrBB++; break;
6125                         case WhiteRook:
6126                              NrWR++; break;
6127                         case BlackRook:
6128                              NrBR++; break;
6129                         case WhiteQueen:
6130                              NrWQ++; break;
6131                         case BlackQueen:
6132                              NrBQ++; break;
6133                         case EmptySquare: 
6134                              break;
6135                         case BlackPawn:
6136                              m = 7-i;
6137                         case WhitePawn:
6138                              PawnAdvance += m; NrPawns++;
6139                     }
6140                     NrPieces += (p != EmptySquare);
6141                     NrW += ((int)p < (int)BlackPawn);
6142                     if(gameInfo.variant == VariantXiangqi && 
6143                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6144                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6145                         NrW -= ((int)p < (int)BlackPawn);
6146                     }
6147                 }
6148
6149                 /* Some material-based adjudications that have to be made before stalemate test */
6150                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6151                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6152                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6153                      if(appData.checkMates) {
6154                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6155                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6156                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6157                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6158                          return;
6159                      }
6160                 }
6161
6162                 /* Bare King in Shatranj (loses) or Losers (wins) */
6163                 if( NrW == 1 || NrPieces - NrW == 1) {
6164                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6165                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6166                      if(appData.checkMates) {
6167                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6168                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6169                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6170                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6171                          return;
6172                      }
6173                   } else
6174                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6175                   {    /* bare King */
6176                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6177                         if(appData.checkMates) {
6178                             /* but only adjudicate if adjudication enabled */
6179                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6180                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6181                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6182                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6183                             return;
6184                         }
6185                   }
6186                 } else bare = 1;
6187
6188
6189             // don't wait for engine to announce game end if we can judge ourselves
6190             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6191                                        castlingRights[forwardMostMove]) ) {
6192               case MT_CHECK:
6193                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6194                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6195                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6196                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6197                             checkCnt++;
6198                         if(checkCnt >= 2) {
6199                             reason = "Xboard adjudication: 3rd check";
6200                             epStatus[forwardMostMove] = EP_CHECKMATE;
6201                             break;
6202                         }
6203                     }
6204                 }
6205               case MT_NONE:
6206               default:
6207                 break;
6208               case MT_STALEMATE:
6209               case MT_STAINMATE:
6210                 reason = "Xboard adjudication: Stalemate";
6211                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6212                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6213                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6214                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6215                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6216                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6217                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6218                                                                         EP_CHECKMATE : EP_WINS);
6219                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6220                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6221                 }
6222                 break;
6223               case MT_CHECKMATE:
6224                 reason = "Xboard adjudication: Checkmate";
6225                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6226                 break;
6227             }
6228
6229                 switch(i = epStatus[forwardMostMove]) {
6230                     case EP_STALEMATE:
6231                         result = GameIsDrawn; break;
6232                     case EP_CHECKMATE:
6233                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6234                     case EP_WINS:
6235                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6236                     default:
6237                         result = (ChessMove) 0;
6238                 }
6239                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6240                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6241                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6242                     GameEnds( result, reason, GE_XBOARD );
6243                     return;
6244                 }
6245
6246                 /* Next absolutely insufficient mating material. */
6247                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6248                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6249                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6250                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6251                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6252
6253                      /* always flag draws, for judging claims */
6254                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6255
6256                      if(appData.materialDraws) {
6257                          /* but only adjudicate them if adjudication enabled */
6258                          SendToProgram("force\n", cps->other); // suppress reply
6259                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6260                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6261                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6262                          return;
6263                      }
6264                 }
6265
6266                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6267                 if(NrPieces == 4 && 
6268                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6269                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6270                    || NrWN==2 || NrBN==2     /* KNNK */
6271                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6272                   ) ) {
6273                      if(--moveCount < 0 && appData.trivialDraws)
6274                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6275                           SendToProgram("force\n", cps->other); // suppress reply
6276                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6277                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6279                           return;
6280                      }
6281                 } else moveCount = 6;
6282             }
6283           }
6284
6285                 /* Check for rep-draws */
6286                 count = 0;
6287                 for(k = forwardMostMove-2;
6288                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6289                         epStatus[k] < EP_UNKNOWN &&
6290                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6291                     k-=2)
6292                 {   int rights=0;
6293                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6294                         /* compare castling rights */
6295                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6296                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6297                                 rights++; /* King lost rights, while rook still had them */
6298                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6299                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6300                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6301                                    rights++; /* but at least one rook lost them */
6302                         }
6303                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6304                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6305                                 rights++; 
6306                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6307                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6308                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6309                                    rights++;
6310                         }
6311                         if( rights == 0 && ++count > appData.drawRepeats-2
6312                             && appData.drawRepeats > 1) {
6313                              /* adjudicate after user-specified nr of repeats */
6314                              SendToProgram("force\n", cps->other); // suppress reply
6315                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6316                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6317                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6318                                 // [HGM] xiangqi: check for forbidden perpetuals
6319                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6320                                 for(m=forwardMostMove; m>k; m-=2) {
6321                                     if(MateTest(boards[m], PosFlags(m), 
6322                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6323                                         ourPerpetual = 0; // the current mover did not always check
6324                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6325                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6326                                         hisPerpetual = 0; // the opponent did not always check
6327                                 }
6328                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6329                                                                         ourPerpetual, hisPerpetual);
6330                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6331                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6332                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6333                                     return;
6334                                 }
6335                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6336                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6337                                 // Now check for perpetual chases
6338                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6339                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6340                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6341                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6342                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6343                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6344                                         return;
6345                                     }
6346                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6347                                         break; // Abort repetition-checking loop.
6348                                 }
6349                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6350                              }
6351                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6352                              return;
6353                         }
6354                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6355                              epStatus[forwardMostMove] = EP_REP_DRAW;
6356                     }
6357                 }
6358
6359                 /* Now we test for 50-move draws. Determine ply count */
6360                 count = forwardMostMove;
6361                 /* look for last irreversble move */
6362                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6363                     count--;
6364                 /* if we hit starting position, add initial plies */
6365                 if( count == backwardMostMove )
6366                     count -= initialRulePlies;
6367                 count = forwardMostMove - count; 
6368                 if( count >= 100)
6369                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6370                          /* this is used to judge if draw claims are legal */
6371                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6372                          SendToProgram("force\n", cps->other); // suppress reply
6373                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6374                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6375                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6376                          return;
6377                 }
6378
6379                 /* if draw offer is pending, treat it as a draw claim
6380                  * when draw condition present, to allow engines a way to
6381                  * claim draws before making their move to avoid a race
6382                  * condition occurring after their move
6383                  */
6384                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6385                          char *p = NULL;
6386                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6387                              p = "Draw claim: 50-move rule";
6388                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6389                              p = "Draw claim: 3-fold repetition";
6390                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6391                              p = "Draw claim: insufficient mating material";
6392                          if( p != NULL ) {
6393                              SendToProgram("force\n", cps->other); // suppress reply
6394                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6395                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6396                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6397                              return;
6398                          }
6399                 }
6400
6401
6402                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6403                     SendToProgram("force\n", cps->other); // suppress reply
6404                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6405                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6406
6407                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6408
6409                     return;
6410                 }
6411         }
6412
6413         bookHit = NULL;
6414         if (gameMode == TwoMachinesPlay) {
6415             /* [HGM] relaying draw offers moved to after reception of move */
6416             /* and interpreting offer as claim if it brings draw condition */
6417             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6418                 SendToProgram("draw\n", cps->other);
6419             }
6420             if (cps->other->sendTime) {
6421                 SendTimeRemaining(cps->other,
6422                                   cps->other->twoMachinesColor[0] == 'w');
6423             }
6424             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6425             if (firstMove && !bookHit) {
6426                 firstMove = FALSE;
6427                 if (cps->other->useColors) {
6428                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6429                 }
6430                 SendToProgram("go\n", cps->other);
6431             }
6432             cps->other->maybeThinking = TRUE;
6433         }
6434
6435         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6436         
6437         if (!pausing && appData.ringBellAfterMoves) {
6438             RingBell();
6439         }
6440
6441         /* 
6442          * Reenable menu items that were disabled while
6443          * machine was thinking
6444          */
6445         if (gameMode != TwoMachinesPlay)
6446             SetUserThinkingEnables();
6447
6448         // [HGM] book: after book hit opponent has received move and is now in force mode
6449         // force the book reply into it, and then fake that it outputted this move by jumping
6450         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6451         if(bookHit) {
6452                 static char bookMove[MSG_SIZ]; // a bit generous?
6453
6454                 strcpy(bookMove, "move ");
6455                 strcat(bookMove, bookHit);
6456                 message = bookMove;
6457                 cps = cps->other;
6458                 programStats.nodes = programStats.depth = programStats.time = 
6459                 programStats.score = programStats.got_only_move = 0;
6460                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6461
6462                 if(cps->lastPing != cps->lastPong) {
6463                     savedMessage = message; // args for deferred call
6464                     savedState = cps;
6465                     ScheduleDelayedEvent(DeferredBookMove, 10);
6466                     return;
6467                 }
6468                 goto FakeBookMove;
6469         }
6470
6471         return;
6472     }
6473
6474     /* Set special modes for chess engines.  Later something general
6475      *  could be added here; for now there is just one kludge feature,
6476      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6477      *  when "xboard" is given as an interactive command.
6478      */
6479     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6480         cps->useSigint = FALSE;
6481         cps->useSigterm = FALSE;
6482     }
6483     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6484       ParseFeatures(message+8, cps);
6485       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6486     }
6487
6488     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6489      * want this, I was asked to put it in, and obliged.
6490      */
6491     if (!strncmp(message, "setboard ", 9)) {
6492         Board initial_position; int i;
6493
6494         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6495
6496         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6497             DisplayError(_("Bad FEN received from engine"), 0);
6498             return ;
6499         } else {
6500            Reset(TRUE, FALSE);
6501            CopyBoard(boards[0], initial_position);
6502            initialRulePlies = FENrulePlies;
6503            epStatus[0] = FENepStatus;
6504            for( i=0; i<nrCastlingRights; i++ )
6505                 castlingRights[0][i] = FENcastlingRights[i];
6506            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6507            else gameMode = MachinePlaysBlack;                 
6508            DrawPosition(FALSE, boards[currentMove]);
6509         }
6510         return;
6511     }
6512
6513     /*
6514      * Look for communication commands
6515      */
6516     if (!strncmp(message, "telluser ", 9)) {
6517         DisplayNote(message + 9);
6518         return;
6519     }
6520     if (!strncmp(message, "tellusererror ", 14)) {
6521         DisplayError(message + 14, 0);
6522         return;
6523     }
6524     if (!strncmp(message, "tellopponent ", 13)) {
6525       if (appData.icsActive) {
6526         if (loggedOn) {
6527           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6528           SendToICS(buf1);
6529         }
6530       } else {
6531         DisplayNote(message + 13);
6532       }
6533       return;
6534     }
6535     if (!strncmp(message, "tellothers ", 11)) {
6536       if (appData.icsActive) {
6537         if (loggedOn) {
6538           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6539           SendToICS(buf1);
6540         }
6541       }
6542       return;
6543     }
6544     if (!strncmp(message, "tellall ", 8)) {
6545       if (appData.icsActive) {
6546         if (loggedOn) {
6547           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6548           SendToICS(buf1);
6549         }
6550       } else {
6551         DisplayNote(message + 8);
6552       }
6553       return;
6554     }
6555     if (strncmp(message, "warning", 7) == 0) {
6556         /* Undocumented feature, use tellusererror in new code */
6557         DisplayError(message, 0);
6558         return;
6559     }
6560     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6561         strcpy(realname, cps->tidy);
6562         strcat(realname, " query");
6563         AskQuestion(realname, buf2, buf1, cps->pr);
6564         return;
6565     }
6566     /* Commands from the engine directly to ICS.  We don't allow these to be 
6567      *  sent until we are logged on. Crafty kibitzes have been known to 
6568      *  interfere with the login process.
6569      */
6570     if (loggedOn) {
6571         if (!strncmp(message, "tellics ", 8)) {
6572             SendToICS(message + 8);
6573             SendToICS("\n");
6574             return;
6575         }
6576         if (!strncmp(message, "tellicsnoalias ", 15)) {
6577             SendToICS(ics_prefix);
6578             SendToICS(message + 15);
6579             SendToICS("\n");
6580             return;
6581         }
6582         /* The following are for backward compatibility only */
6583         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6584             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6585             SendToICS(ics_prefix);
6586             SendToICS(message);
6587             SendToICS("\n");
6588             return;
6589         }
6590     }
6591     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6592         return;
6593     }
6594     /*
6595      * If the move is illegal, cancel it and redraw the board.
6596      * Also deal with other error cases.  Matching is rather loose
6597      * here to accommodate engines written before the spec.
6598      */
6599     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6600         strncmp(message, "Error", 5) == 0) {
6601         if (StrStr(message, "name") || 
6602             StrStr(message, "rating") || StrStr(message, "?") ||
6603             StrStr(message, "result") || StrStr(message, "board") ||
6604             StrStr(message, "bk") || StrStr(message, "computer") ||
6605             StrStr(message, "variant") || StrStr(message, "hint") ||
6606             StrStr(message, "random") || StrStr(message, "depth") ||
6607             StrStr(message, "accepted")) {
6608             return;
6609         }
6610         if (StrStr(message, "protover")) {
6611           /* Program is responding to input, so it's apparently done
6612              initializing, and this error message indicates it is
6613              protocol version 1.  So we don't need to wait any longer
6614              for it to initialize and send feature commands. */
6615           FeatureDone(cps, 1);
6616           cps->protocolVersion = 1;
6617           return;
6618         }
6619         cps->maybeThinking = FALSE;
6620
6621         if (StrStr(message, "draw")) {
6622             /* Program doesn't have "draw" command */
6623             cps->sendDrawOffers = 0;
6624             return;
6625         }
6626         if (cps->sendTime != 1 &&
6627             (StrStr(message, "time") || StrStr(message, "otim"))) {
6628           /* Program apparently doesn't have "time" or "otim" command */
6629           cps->sendTime = 0;
6630           return;
6631         }
6632         if (StrStr(message, "analyze")) {
6633             cps->analysisSupport = FALSE;
6634             cps->analyzing = FALSE;
6635             Reset(FALSE, TRUE);
6636             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6637             DisplayError(buf2, 0);
6638             return;
6639         }
6640         if (StrStr(message, "(no matching move)st")) {
6641           /* Special kludge for GNU Chess 4 only */
6642           cps->stKludge = TRUE;
6643           SendTimeControl(cps, movesPerSession, timeControl,
6644                           timeIncrement, appData.searchDepth,
6645                           searchTime);
6646           return;
6647         }
6648         if (StrStr(message, "(no matching move)sd")) {
6649           /* Special kludge for GNU Chess 4 only */
6650           cps->sdKludge = TRUE;
6651           SendTimeControl(cps, movesPerSession, timeControl,
6652                           timeIncrement, appData.searchDepth,
6653                           searchTime);
6654           return;
6655         }
6656         if (!StrStr(message, "llegal")) {
6657             return;
6658         }
6659         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6660             gameMode == IcsIdle) return;
6661         if (forwardMostMove <= backwardMostMove) return;
6662         if (pausing) PauseEvent();
6663       if(appData.forceIllegal) {
6664             // [HGM] illegal: machine refused move; force position after move into it
6665           SendToProgram("force\n", cps);
6666           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6667                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6668                 // when black is to move, while there might be nothing on a2 or black
6669                 // might already have the move. So send the board as if white has the move.
6670                 // But first we must change the stm of the engine, as it refused the last move
6671                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6672                 if(WhiteOnMove(forwardMostMove)) {
6673                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6674                     SendBoard(cps, forwardMostMove); // kludgeless board
6675                 } else {
6676                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6677                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6678                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6679                 }
6680           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6681             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6682                  gameMode == TwoMachinesPlay)
6683               SendToProgram("go\n", cps);
6684             return;
6685       } else
6686         if (gameMode == PlayFromGameFile) {
6687             /* Stop reading this game file */
6688             gameMode = EditGame;
6689             ModeHighlight();
6690         }
6691         currentMove = forwardMostMove-1;
6692         DisplayMove(currentMove-1); /* before DisplayMoveError */
6693         SwitchClocks(forwardMostMove-1); // [HGM] race
6694         DisplayBothClocks();
6695         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6696                 parseList[currentMove], cps->which);
6697         DisplayMoveError(buf1);
6698         DrawPosition(FALSE, boards[currentMove]);
6699
6700         /* [HGM] illegal-move claim should forfeit game when Xboard */
6701         /* only passes fully legal moves                            */
6702         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6703             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6704                                 "False illegal-move claim", GE_XBOARD );
6705         }
6706         return;
6707     }
6708     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6709         /* Program has a broken "time" command that
6710            outputs a string not ending in newline.
6711            Don't use it. */
6712         cps->sendTime = 0;
6713     }
6714     
6715     /*
6716      * If chess program startup fails, exit with an error message.
6717      * Attempts to recover here are futile.
6718      */
6719     if ((StrStr(message, "unknown host") != NULL)
6720         || (StrStr(message, "No remote directory") != NULL)
6721         || (StrStr(message, "not found") != NULL)
6722         || (StrStr(message, "No such file") != NULL)
6723         || (StrStr(message, "can't alloc") != NULL)
6724         || (StrStr(message, "Permission denied") != NULL)) {
6725
6726         cps->maybeThinking = FALSE;
6727         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6728                 cps->which, cps->program, cps->host, message);
6729         RemoveInputSource(cps->isr);
6730         DisplayFatalError(buf1, 0, 1);
6731         return;
6732     }
6733     
6734     /* 
6735      * Look for hint output
6736      */
6737     if (sscanf(message, "Hint: %s", buf1) == 1) {
6738         if (cps == &first && hintRequested) {
6739             hintRequested = FALSE;
6740             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6741                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6742                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6743                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6744                                     fromY, fromX, toY, toX, promoChar, buf1);
6745                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6746                 DisplayInformation(buf2);
6747             } else {
6748                 /* Hint move could not be parsed!? */
6749               snprintf(buf2, sizeof(buf2),
6750                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6751                         buf1, cps->which);
6752                 DisplayError(buf2, 0);
6753             }
6754         } else {
6755             strcpy(lastHint, buf1);
6756         }
6757         return;
6758     }
6759
6760     /*
6761      * Ignore other messages if game is not in progress
6762      */
6763     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6764         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6765
6766     /*
6767      * look for win, lose, draw, or draw offer
6768      */
6769     if (strncmp(message, "1-0", 3) == 0) {
6770         char *p, *q, *r = "";
6771         p = strchr(message, '{');
6772         if (p) {
6773             q = strchr(p, '}');
6774             if (q) {
6775                 *q = NULLCHAR;
6776                 r = p + 1;
6777             }
6778         }
6779         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6780         return;
6781     } else if (strncmp(message, "0-1", 3) == 0) {
6782         char *p, *q, *r = "";
6783         p = strchr(message, '{');
6784         if (p) {
6785             q = strchr(p, '}');
6786             if (q) {
6787                 *q = NULLCHAR;
6788                 r = p + 1;
6789             }
6790         }
6791         /* Kludge for Arasan 4.1 bug */
6792         if (strcmp(r, "Black resigns") == 0) {
6793             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6794             return;
6795         }
6796         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6797         return;
6798     } else if (strncmp(message, "1/2", 3) == 0) {
6799         char *p, *q, *r = "";
6800         p = strchr(message, '{');
6801         if (p) {
6802             q = strchr(p, '}');
6803             if (q) {
6804                 *q = NULLCHAR;
6805                 r = p + 1;
6806             }
6807         }
6808             
6809         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6810         return;
6811
6812     } else if (strncmp(message, "White resign", 12) == 0) {
6813         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6814         return;
6815     } else if (strncmp(message, "Black resign", 12) == 0) {
6816         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6817         return;
6818     } else if (strncmp(message, "White matches", 13) == 0 ||
6819                strncmp(message, "Black matches", 13) == 0   ) {
6820         /* [HGM] ignore GNUShogi noises */
6821         return;
6822     } else if (strncmp(message, "White", 5) == 0 &&
6823                message[5] != '(' &&
6824                StrStr(message, "Black") == NULL) {
6825         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6826         return;
6827     } else if (strncmp(message, "Black", 5) == 0 &&
6828                message[5] != '(') {
6829         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6830         return;
6831     } else if (strcmp(message, "resign") == 0 ||
6832                strcmp(message, "computer resigns") == 0) {
6833         switch (gameMode) {
6834           case MachinePlaysBlack:
6835           case IcsPlayingBlack:
6836             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6837             break;
6838           case MachinePlaysWhite:
6839           case IcsPlayingWhite:
6840             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6841             break;
6842           case TwoMachinesPlay:
6843             if (cps->twoMachinesColor[0] == 'w')
6844               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6845             else
6846               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6847             break;
6848           default:
6849             /* can't happen */
6850             break;
6851         }
6852         return;
6853     } else if (strncmp(message, "opponent mates", 14) == 0) {
6854         switch (gameMode) {
6855           case MachinePlaysBlack:
6856           case IcsPlayingBlack:
6857             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6858             break;
6859           case MachinePlaysWhite:
6860           case IcsPlayingWhite:
6861             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6862             break;
6863           case TwoMachinesPlay:
6864             if (cps->twoMachinesColor[0] == 'w')
6865               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6866             else
6867               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6868             break;
6869           default:
6870             /* can't happen */
6871             break;
6872         }
6873         return;
6874     } else if (strncmp(message, "computer mates", 14) == 0) {
6875         switch (gameMode) {
6876           case MachinePlaysBlack:
6877           case IcsPlayingBlack:
6878             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6879             break;
6880           case MachinePlaysWhite:
6881           case IcsPlayingWhite:
6882             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6883             break;
6884           case TwoMachinesPlay:
6885             if (cps->twoMachinesColor[0] == 'w')
6886               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6887             else
6888               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6889             break;
6890           default:
6891             /* can't happen */
6892             break;
6893         }
6894         return;
6895     } else if (strncmp(message, "checkmate", 9) == 0) {
6896         if (WhiteOnMove(forwardMostMove)) {
6897             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6898         } else {
6899             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6900         }
6901         return;
6902     } else if (strstr(message, "Draw") != NULL ||
6903                strstr(message, "game is a draw") != NULL) {
6904         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6905         return;
6906     } else if (strstr(message, "offer") != NULL &&
6907                strstr(message, "draw") != NULL) {
6908 #if ZIPPY
6909         if (appData.zippyPlay && first.initDone) {
6910             /* Relay offer to ICS */
6911             SendToICS(ics_prefix);
6912             SendToICS("draw\n");
6913         }
6914 #endif
6915         cps->offeredDraw = 2; /* valid until this engine moves twice */
6916         if (gameMode == TwoMachinesPlay) {
6917             if (cps->other->offeredDraw) {
6918                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6919             /* [HGM] in two-machine mode we delay relaying draw offer      */
6920             /* until after we also have move, to see if it is really claim */
6921             }
6922         } else if (gameMode == MachinePlaysWhite ||
6923                    gameMode == MachinePlaysBlack) {
6924           if (userOfferedDraw) {
6925             DisplayInformation(_("Machine accepts your draw offer"));
6926             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6927           } else {
6928             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6929           }
6930         }
6931     }
6932
6933     
6934     /*
6935      * Look for thinking output
6936      */
6937     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6938           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6939                                 ) {
6940         int plylev, mvleft, mvtot, curscore, time;
6941         char mvname[MOVE_LEN];
6942         u64 nodes; // [DM]
6943         char plyext;
6944         int ignore = FALSE;
6945         int prefixHint = FALSE;
6946         mvname[0] = NULLCHAR;
6947
6948         switch (gameMode) {
6949           case MachinePlaysBlack:
6950           case IcsPlayingBlack:
6951             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6952             break;
6953           case MachinePlaysWhite:
6954           case IcsPlayingWhite:
6955             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6956             break;
6957           case AnalyzeMode:
6958           case AnalyzeFile:
6959             break;
6960           case IcsObserving: /* [DM] icsEngineAnalyze */
6961             if (!appData.icsEngineAnalyze) ignore = TRUE;
6962             break;
6963           case TwoMachinesPlay:
6964             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6965                 ignore = TRUE;
6966             }
6967             break;
6968           default:
6969             ignore = TRUE;
6970             break;
6971         }
6972
6973         if (!ignore) {
6974             buf1[0] = NULLCHAR;
6975             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6976                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6977
6978                 if (plyext != ' ' && plyext != '\t') {
6979                     time *= 100;
6980                 }
6981
6982                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6983                 if( cps->scoreIsAbsolute && 
6984                     ( gameMode == MachinePlaysBlack ||
6985                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6986                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6987                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6988                      !WhiteOnMove(currentMove)
6989                     ) )
6990                 {
6991                     curscore = -curscore;
6992                 }
6993
6994
6995                 programStats.depth = plylev;
6996                 programStats.nodes = nodes;
6997                 programStats.time = time;
6998                 programStats.score = curscore;
6999                 programStats.got_only_move = 0;
7000
7001                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7002                         int ticklen;
7003
7004                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7005                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7006                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7007                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7008                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7009                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7010                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7011                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7012                 }
7013
7014                 /* Buffer overflow protection */
7015                 if (buf1[0] != NULLCHAR) {
7016                     if (strlen(buf1) >= sizeof(programStats.movelist)
7017                         && appData.debugMode) {
7018                         fprintf(debugFP,
7019                                 "PV is too long; using the first %u bytes.\n",
7020                                 (unsigned) sizeof(programStats.movelist) - 1);
7021                     }
7022
7023                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7024                 } else {
7025                     sprintf(programStats.movelist, " no PV\n");
7026                 }
7027
7028                 if (programStats.seen_stat) {
7029                     programStats.ok_to_send = 1;
7030                 }
7031
7032                 if (strchr(programStats.movelist, '(') != NULL) {
7033                     programStats.line_is_book = 1;
7034                     programStats.nr_moves = 0;
7035                     programStats.moves_left = 0;
7036                 } else {
7037                     programStats.line_is_book = 0;
7038                 }
7039
7040                 SendProgramStatsToFrontend( cps, &programStats );
7041
7042                 /* 
7043                     [AS] Protect the thinkOutput buffer from overflow... this
7044                     is only useful if buf1 hasn't overflowed first!
7045                 */
7046                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7047                         plylev, 
7048                         (gameMode == TwoMachinesPlay ?
7049                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7050                         ((double) curscore) / 100.0,
7051                         prefixHint ? lastHint : "",
7052                         prefixHint ? " " : "" );
7053
7054                 if( buf1[0] != NULLCHAR ) {
7055                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7056
7057                     if( strlen(buf1) > max_len ) {
7058                         if( appData.debugMode) {
7059                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7060                         }
7061                         buf1[max_len+1] = '\0';
7062                     }
7063
7064                     strcat( thinkOutput, buf1 );
7065                 }
7066
7067                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7068                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7069                     DisplayMove(currentMove - 1);
7070                 }
7071                 return;
7072
7073             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7074                 /* crafty (9.25+) says "(only move) <move>"
7075                  * if there is only 1 legal move
7076                  */
7077                 sscanf(p, "(only move) %s", buf1);
7078                 sprintf(thinkOutput, "%s (only move)", buf1);
7079                 sprintf(programStats.movelist, "%s (only move)", buf1);
7080                 programStats.depth = 1;
7081                 programStats.nr_moves = 1;
7082                 programStats.moves_left = 1;
7083                 programStats.nodes = 1;
7084                 programStats.time = 1;
7085                 programStats.got_only_move = 1;
7086
7087                 /* Not really, but we also use this member to
7088                    mean "line isn't going to change" (Crafty
7089                    isn't searching, so stats won't change) */
7090                 programStats.line_is_book = 1;
7091
7092                 SendProgramStatsToFrontend( cps, &programStats );
7093                 
7094                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7095                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7096                     DisplayMove(currentMove - 1);
7097                 }
7098                 return;
7099             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7100                               &time, &nodes, &plylev, &mvleft,
7101                               &mvtot, mvname) >= 5) {
7102                 /* The stat01: line is from Crafty (9.29+) in response
7103                    to the "." command */
7104                 programStats.seen_stat = 1;
7105                 cps->maybeThinking = TRUE;
7106
7107                 if (programStats.got_only_move || !appData.periodicUpdates)
7108                   return;
7109
7110                 programStats.depth = plylev;
7111                 programStats.time = time;
7112                 programStats.nodes = nodes;
7113                 programStats.moves_left = mvleft;
7114                 programStats.nr_moves = mvtot;
7115                 strcpy(programStats.move_name, mvname);
7116                 programStats.ok_to_send = 1;
7117                 programStats.movelist[0] = '\0';
7118
7119                 SendProgramStatsToFrontend( cps, &programStats );
7120
7121                 return;
7122
7123             } else if (strncmp(message,"++",2) == 0) {
7124                 /* Crafty 9.29+ outputs this */
7125                 programStats.got_fail = 2;
7126                 return;
7127
7128             } else if (strncmp(message,"--",2) == 0) {
7129                 /* Crafty 9.29+ outputs this */
7130                 programStats.got_fail = 1;
7131                 return;
7132
7133             } else if (thinkOutput[0] != NULLCHAR &&
7134                        strncmp(message, "    ", 4) == 0) {
7135                 unsigned message_len;
7136
7137                 p = message;
7138                 while (*p && *p == ' ') p++;
7139
7140                 message_len = strlen( p );
7141
7142                 /* [AS] Avoid buffer overflow */
7143                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7144                     strcat(thinkOutput, " ");
7145                     strcat(thinkOutput, p);
7146                 }
7147
7148                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7149                     strcat(programStats.movelist, " ");
7150                     strcat(programStats.movelist, p);
7151                 }
7152
7153                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7154                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7155                     DisplayMove(currentMove - 1);
7156                 }
7157                 return;
7158             }
7159         }
7160         else {
7161             buf1[0] = NULLCHAR;
7162
7163             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7164                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7165             {
7166                 ChessProgramStats cpstats;
7167
7168                 if (plyext != ' ' && plyext != '\t') {
7169                     time *= 100;
7170                 }
7171
7172                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7173                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7174                     curscore = -curscore;
7175                 }
7176
7177                 cpstats.depth = plylev;
7178                 cpstats.nodes = nodes;
7179                 cpstats.time = time;
7180                 cpstats.score = curscore;
7181                 cpstats.got_only_move = 0;
7182                 cpstats.movelist[0] = '\0';
7183
7184                 if (buf1[0] != NULLCHAR) {
7185                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7186                 }
7187
7188                 cpstats.ok_to_send = 0;
7189                 cpstats.line_is_book = 0;
7190                 cpstats.nr_moves = 0;
7191                 cpstats.moves_left = 0;
7192
7193                 SendProgramStatsToFrontend( cps, &cpstats );
7194             }
7195         }
7196     }
7197 }
7198
7199
7200 /* Parse a game score from the character string "game", and
7201    record it as the history of the current game.  The game
7202    score is NOT assumed to start from the standard position. 
7203    The display is not updated in any way.
7204    */
7205 void
7206 ParseGameHistory(game)
7207      char *game;
7208 {
7209     ChessMove moveType;
7210     int fromX, fromY, toX, toY, boardIndex;
7211     char promoChar;
7212     char *p, *q;
7213     char buf[MSG_SIZ];
7214
7215     if (appData.debugMode)
7216       fprintf(debugFP, "Parsing game history: %s\n", game);
7217
7218     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7219     gameInfo.site = StrSave(appData.icsHost);
7220     gameInfo.date = PGNDate();
7221     gameInfo.round = StrSave("-");
7222
7223     /* Parse out names of players */
7224     while (*game == ' ') game++;
7225     p = buf;
7226     while (*game != ' ') *p++ = *game++;
7227     *p = NULLCHAR;
7228     gameInfo.white = StrSave(buf);
7229     while (*game == ' ') game++;
7230     p = buf;
7231     while (*game != ' ' && *game != '\n') *p++ = *game++;
7232     *p = NULLCHAR;
7233     gameInfo.black = StrSave(buf);
7234
7235     /* Parse moves */
7236     boardIndex = blackPlaysFirst ? 1 : 0;
7237     yynewstr(game);
7238     for (;;) {
7239         yyboardindex = boardIndex;
7240         moveType = (ChessMove) yylex();
7241         switch (moveType) {
7242           case IllegalMove:             /* maybe suicide chess, etc. */
7243   if (appData.debugMode) {
7244     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7246     setbuf(debugFP, NULL);
7247   }
7248           case WhitePromotionChancellor:
7249           case BlackPromotionChancellor:
7250           case WhitePromotionArchbishop:
7251           case BlackPromotionArchbishop:
7252           case WhitePromotionQueen:
7253           case BlackPromotionQueen:
7254           case WhitePromotionRook:
7255           case BlackPromotionRook:
7256           case WhitePromotionBishop:
7257           case BlackPromotionBishop:
7258           case WhitePromotionKnight:
7259           case BlackPromotionKnight:
7260           case WhitePromotionKing:
7261           case BlackPromotionKing:
7262           case NormalMove:
7263           case WhiteCapturesEnPassant:
7264           case BlackCapturesEnPassant:
7265           case WhiteKingSideCastle:
7266           case WhiteQueenSideCastle:
7267           case BlackKingSideCastle:
7268           case BlackQueenSideCastle:
7269           case WhiteKingSideCastleWild:
7270           case WhiteQueenSideCastleWild:
7271           case BlackKingSideCastleWild:
7272           case BlackQueenSideCastleWild:
7273           /* PUSH Fabien */
7274           case WhiteHSideCastleFR:
7275           case WhiteASideCastleFR:
7276           case BlackHSideCastleFR:
7277           case BlackASideCastleFR:
7278           /* POP Fabien */
7279             fromX = currentMoveString[0] - AAA;
7280             fromY = currentMoveString[1] - ONE;
7281             toX = currentMoveString[2] - AAA;
7282             toY = currentMoveString[3] - ONE;
7283             promoChar = currentMoveString[4];
7284             break;
7285           case WhiteDrop:
7286           case BlackDrop:
7287             fromX = moveType == WhiteDrop ?
7288               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7289             (int) CharToPiece(ToLower(currentMoveString[0]));
7290             fromY = DROP_RANK;
7291             toX = currentMoveString[2] - AAA;
7292             toY = currentMoveString[3] - ONE;
7293             promoChar = NULLCHAR;
7294             break;
7295           case AmbiguousMove:
7296             /* bug? */
7297             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7298   if (appData.debugMode) {
7299     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7300     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7301     setbuf(debugFP, NULL);
7302   }
7303             DisplayError(buf, 0);
7304             return;
7305           case ImpossibleMove:
7306             /* bug? */
7307             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7308   if (appData.debugMode) {
7309     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7310     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7311     setbuf(debugFP, NULL);
7312   }
7313             DisplayError(buf, 0);
7314             return;
7315           case (ChessMove) 0:   /* end of file */
7316             if (boardIndex < backwardMostMove) {
7317                 /* Oops, gap.  How did that happen? */
7318                 DisplayError(_("Gap in move list"), 0);
7319                 return;
7320             }
7321             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7322             if (boardIndex > forwardMostMove) {
7323                 forwardMostMove = boardIndex;
7324             }
7325             return;
7326           case ElapsedTime:
7327             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7328                 strcat(parseList[boardIndex-1], " ");
7329                 strcat(parseList[boardIndex-1], yy_text);
7330             }
7331             continue;
7332           case Comment:
7333           case PGNTag:
7334           case NAG:
7335           default:
7336             /* ignore */
7337             continue;
7338           case WhiteWins:
7339           case BlackWins:
7340           case GameIsDrawn:
7341           case GameUnfinished:
7342             if (gameMode == IcsExamining) {
7343                 if (boardIndex < backwardMostMove) {
7344                     /* Oops, gap.  How did that happen? */
7345                     return;
7346                 }
7347                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7348                 return;
7349             }
7350             gameInfo.result = moveType;
7351             p = strchr(yy_text, '{');
7352             if (p == NULL) p = strchr(yy_text, '(');
7353             if (p == NULL) {
7354                 p = yy_text;
7355                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7356             } else {
7357                 q = strchr(p, *p == '{' ? '}' : ')');
7358                 if (q != NULL) *q = NULLCHAR;
7359                 p++;
7360             }
7361             gameInfo.resultDetails = StrSave(p);
7362             continue;
7363         }
7364         if (boardIndex >= forwardMostMove &&
7365             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7366             backwardMostMove = blackPlaysFirst ? 1 : 0;
7367             return;
7368         }
7369         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7370                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7371                                  parseList[boardIndex]);
7372         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7373         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7374         /* currentMoveString is set as a side-effect of yylex */
7375         strcpy(moveList[boardIndex], currentMoveString);
7376         strcat(moveList[boardIndex], "\n");
7377         boardIndex++;
7378         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7379                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7380         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7381                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7382           case MT_NONE:
7383           case MT_STALEMATE:
7384           default:
7385             break;
7386           case MT_CHECK:
7387             if(gameInfo.variant != VariantShogi)
7388                 strcat(parseList[boardIndex - 1], "+");
7389             break;
7390           case MT_CHECKMATE:
7391           case MT_STAINMATE:
7392             strcat(parseList[boardIndex - 1], "#");
7393             break;
7394         }
7395     }
7396 }
7397
7398
7399 /* Apply a move to the given board  */
7400 void
7401 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7402      int fromX, fromY, toX, toY;
7403      int promoChar;
7404      Board board;
7405      char *castling;
7406      char *ep;
7407 {
7408   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7409   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7410
7411     /* [HGM] compute & store e.p. status and castling rights for new position */
7412     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7413     { int i;
7414
7415       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7416       oldEP = *ep;
7417       *ep = EP_NONE;
7418
7419       if( board[toY][toX] != EmptySquare ) 
7420            *ep = EP_CAPTURE;  
7421
7422       if( board[fromY][fromX] == WhitePawn ) {
7423            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7424                *ep = EP_PAWN_MOVE;
7425            if( toY-fromY==2) {
7426                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7427                         gameInfo.variant != VariantBerolina || toX < fromX)
7428                       *ep = toX | berolina;
7429                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7430                         gameInfo.variant != VariantBerolina || toX > fromX) 
7431                       *ep = toX;
7432            }
7433       } else 
7434       if( board[fromY][fromX] == BlackPawn ) {
7435            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7436                *ep = EP_PAWN_MOVE; 
7437            if( toY-fromY== -2) {
7438                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7439                         gameInfo.variant != VariantBerolina || toX < fromX)
7440                       *ep = toX | berolina;
7441                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7442                         gameInfo.variant != VariantBerolina || toX > fromX) 
7443                       *ep = toX;
7444            }
7445        }
7446
7447        for(i=0; i<nrCastlingRights; i++) {
7448            if(castling[i] == fromX && castlingRank[i] == fromY ||
7449               castling[i] == toX   && castlingRank[i] == toY   
7450              ) castling[i] = -1; // revoke for moved or captured piece
7451        }
7452
7453     }
7454
7455   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7456   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7457        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7458          
7459   if (fromX == toX && fromY == toY) return;
7460
7461   if (fromY == DROP_RANK) {
7462         /* must be first */
7463         piece = board[toY][toX] = (ChessSquare) fromX;
7464   } else {
7465      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7466      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7467      if(gameInfo.variant == VariantKnightmate)
7468          king += (int) WhiteUnicorn - (int) WhiteKing;
7469
7470     /* Code added by Tord: */
7471     /* FRC castling assumed when king captures friendly rook. */
7472     if (board[fromY][fromX] == WhiteKing &&
7473              board[toY][toX] == WhiteRook) {
7474       board[fromY][fromX] = EmptySquare;
7475       board[toY][toX] = EmptySquare;
7476       if(toX > fromX) {
7477         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7478       } else {
7479         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7480       }
7481     } else if (board[fromY][fromX] == BlackKing &&
7482                board[toY][toX] == BlackRook) {
7483       board[fromY][fromX] = EmptySquare;
7484       board[toY][toX] = EmptySquare;
7485       if(toX > fromX) {
7486         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7487       } else {
7488         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7489       }
7490     /* End of code added by Tord */
7491
7492     } else if (board[fromY][fromX] == king
7493         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7494         && toY == fromY && toX > fromX+1) {
7495         board[fromY][fromX] = EmptySquare;
7496         board[toY][toX] = king;
7497         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7498         board[fromY][BOARD_RGHT-1] = EmptySquare;
7499     } else if (board[fromY][fromX] == king
7500         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7501                && toY == fromY && toX < fromX-1) {
7502         board[fromY][fromX] = EmptySquare;
7503         board[toY][toX] = king;
7504         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7505         board[fromY][BOARD_LEFT] = EmptySquare;
7506     } else if (board[fromY][fromX] == WhitePawn
7507                && toY >= BOARD_HEIGHT-promoRank
7508                && gameInfo.variant != VariantXiangqi
7509                ) {
7510         /* white pawn promotion */
7511         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7512         if (board[toY][toX] == EmptySquare) {
7513             board[toY][toX] = WhiteQueen;
7514         }
7515         if(gameInfo.variant==VariantBughouse ||
7516            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7517             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7518         board[fromY][fromX] = EmptySquare;
7519     } else if ((fromY == BOARD_HEIGHT-4)
7520                && (toX != fromX)
7521                && gameInfo.variant != VariantXiangqi
7522                && gameInfo.variant != VariantBerolina
7523                && (board[fromY][fromX] == WhitePawn)
7524                && (board[toY][toX] == EmptySquare)) {
7525         board[fromY][fromX] = EmptySquare;
7526         board[toY][toX] = WhitePawn;
7527         captured = board[toY - 1][toX];
7528         board[toY - 1][toX] = EmptySquare;
7529     } else if ((fromY == BOARD_HEIGHT-4)
7530                && (toX == fromX)
7531                && gameInfo.variant == VariantBerolina
7532                && (board[fromY][fromX] == WhitePawn)
7533                && (board[toY][toX] == EmptySquare)) {
7534         board[fromY][fromX] = EmptySquare;
7535         board[toY][toX] = WhitePawn;
7536         if(oldEP & EP_BEROLIN_A) {
7537                 captured = board[fromY][fromX-1];
7538                 board[fromY][fromX-1] = EmptySquare;
7539         }else{  captured = board[fromY][fromX+1];
7540                 board[fromY][fromX+1] = EmptySquare;
7541         }
7542     } else if (board[fromY][fromX] == king
7543         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7544                && toY == fromY && toX > fromX+1) {
7545         board[fromY][fromX] = EmptySquare;
7546         board[toY][toX] = king;
7547         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7548         board[fromY][BOARD_RGHT-1] = EmptySquare;
7549     } else if (board[fromY][fromX] == king
7550         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7551                && toY == fromY && toX < fromX-1) {
7552         board[fromY][fromX] = EmptySquare;
7553         board[toY][toX] = king;
7554         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7555         board[fromY][BOARD_LEFT] = EmptySquare;
7556     } else if (fromY == 7 && fromX == 3
7557                && board[fromY][fromX] == BlackKing
7558                && toY == 7 && toX == 5) {
7559         board[fromY][fromX] = EmptySquare;
7560         board[toY][toX] = BlackKing;
7561         board[fromY][7] = EmptySquare;
7562         board[toY][4] = BlackRook;
7563     } else if (fromY == 7 && fromX == 3
7564                && board[fromY][fromX] == BlackKing
7565                && toY == 7 && toX == 1) {
7566         board[fromY][fromX] = EmptySquare;
7567         board[toY][toX] = BlackKing;
7568         board[fromY][0] = EmptySquare;
7569         board[toY][2] = BlackRook;
7570     } else if (board[fromY][fromX] == BlackPawn
7571                && toY < promoRank
7572                && gameInfo.variant != VariantXiangqi
7573                ) {
7574         /* black pawn promotion */
7575         board[toY][toX] = CharToPiece(ToLower(promoChar));
7576         if (board[toY][toX] == EmptySquare) {
7577             board[toY][toX] = BlackQueen;
7578         }
7579         if(gameInfo.variant==VariantBughouse ||
7580            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7581             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7582         board[fromY][fromX] = EmptySquare;
7583     } else if ((fromY == 3)
7584                && (toX != fromX)
7585                && gameInfo.variant != VariantXiangqi
7586                && gameInfo.variant != VariantBerolina
7587                && (board[fromY][fromX] == BlackPawn)
7588                && (board[toY][toX] == EmptySquare)) {
7589         board[fromY][fromX] = EmptySquare;
7590         board[toY][toX] = BlackPawn;
7591         captured = board[toY + 1][toX];
7592         board[toY + 1][toX] = EmptySquare;
7593     } else if ((fromY == 3)
7594                && (toX == fromX)
7595                && gameInfo.variant == VariantBerolina
7596                && (board[fromY][fromX] == BlackPawn)
7597                && (board[toY][toX] == EmptySquare)) {
7598         board[fromY][fromX] = EmptySquare;
7599         board[toY][toX] = BlackPawn;
7600         if(oldEP & EP_BEROLIN_A) {
7601                 captured = board[fromY][fromX-1];
7602                 board[fromY][fromX-1] = EmptySquare;
7603         }else{  captured = board[fromY][fromX+1];
7604                 board[fromY][fromX+1] = EmptySquare;
7605         }
7606     } else {
7607         board[toY][toX] = board[fromY][fromX];
7608         board[fromY][fromX] = EmptySquare;
7609     }
7610
7611     /* [HGM] now we promote for Shogi, if needed */
7612     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7613         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7614   }
7615
7616     if (gameInfo.holdingsWidth != 0) {
7617
7618       /* !!A lot more code needs to be written to support holdings  */
7619       /* [HGM] OK, so I have written it. Holdings are stored in the */
7620       /* penultimate board files, so they are automaticlly stored   */
7621       /* in the game history.                                       */
7622       if (fromY == DROP_RANK) {
7623         /* Delete from holdings, by decreasing count */
7624         /* and erasing image if necessary            */
7625         p = (int) fromX;
7626         if(p < (int) BlackPawn) { /* white drop */
7627              p -= (int)WhitePawn;
7628                  p = PieceToNumber((ChessSquare)p);
7629              if(p >= gameInfo.holdingsSize) p = 0;
7630              if(--board[p][BOARD_WIDTH-2] <= 0)
7631                   board[p][BOARD_WIDTH-1] = EmptySquare;
7632              if((int)board[p][BOARD_WIDTH-2] < 0)
7633                         board[p][BOARD_WIDTH-2] = 0;
7634         } else {                  /* black drop */
7635              p -= (int)BlackPawn;
7636                  p = PieceToNumber((ChessSquare)p);
7637              if(p >= gameInfo.holdingsSize) p = 0;
7638              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7639                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7640              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7641                         board[BOARD_HEIGHT-1-p][1] = 0;
7642         }
7643       }
7644       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7645           && gameInfo.variant != VariantBughouse        ) {
7646         /* [HGM] holdings: Add to holdings, if holdings exist */
7647         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7648                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7649                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7650         }
7651         p = (int) captured;
7652         if (p >= (int) BlackPawn) {
7653           p -= (int)BlackPawn;
7654           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7655                   /* in Shogi restore piece to its original  first */
7656                   captured = (ChessSquare) (DEMOTED captured);
7657                   p = DEMOTED p;
7658           }
7659           p = PieceToNumber((ChessSquare)p);
7660           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7661           board[p][BOARD_WIDTH-2]++;
7662           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7663         } else {
7664           p -= (int)WhitePawn;
7665           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7666                   captured = (ChessSquare) (DEMOTED captured);
7667                   p = DEMOTED p;
7668           }
7669           p = PieceToNumber((ChessSquare)p);
7670           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7671           board[BOARD_HEIGHT-1-p][1]++;
7672           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7673         }
7674       }
7675     } else if (gameInfo.variant == VariantAtomic) {
7676       if (captured != EmptySquare) {
7677         int y, x;
7678         for (y = toY-1; y <= toY+1; y++) {
7679           for (x = toX-1; x <= toX+1; x++) {
7680             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7681                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7682               board[y][x] = EmptySquare;
7683             }
7684           }
7685         }
7686         board[toY][toX] = EmptySquare;
7687       }
7688     }
7689     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7690         /* [HGM] Shogi promotions */
7691         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7692     }
7693
7694     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7695                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7696         // [HGM] superchess: take promotion piece out of holdings
7697         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7698         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7699             if(!--board[k][BOARD_WIDTH-2])
7700                 board[k][BOARD_WIDTH-1] = EmptySquare;
7701         } else {
7702             if(!--board[BOARD_HEIGHT-1-k][1])
7703                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7704         }
7705     }
7706
7707 }
7708
7709 /* Updates forwardMostMove */
7710 void
7711 MakeMove(fromX, fromY, toX, toY, promoChar)
7712      int fromX, fromY, toX, toY;
7713      int promoChar;
7714 {
7715 //    forwardMostMove++; // [HGM] bare: moved downstream
7716
7717     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7718         int timeLeft; static int lastLoadFlag=0; int king, piece;
7719         piece = boards[forwardMostMove][fromY][fromX];
7720         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7721         if(gameInfo.variant == VariantKnightmate)
7722             king += (int) WhiteUnicorn - (int) WhiteKing;
7723         if(forwardMostMove == 0) {
7724             if(blackPlaysFirst) 
7725                 fprintf(serverMoves, "%s;", second.tidy);
7726             fprintf(serverMoves, "%s;", first.tidy);
7727             if(!blackPlaysFirst) 
7728                 fprintf(serverMoves, "%s;", second.tidy);
7729         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7730         lastLoadFlag = loadFlag;
7731         // print base move
7732         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7733         // print castling suffix
7734         if( toY == fromY && piece == king ) {
7735             if(toX-fromX > 1)
7736                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7737             if(fromX-toX >1)
7738                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7739         }
7740         // e.p. suffix
7741         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7742              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7743              boards[forwardMostMove][toY][toX] == EmptySquare
7744              && fromX != toX )
7745                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7746         // promotion suffix
7747         if(promoChar != NULLCHAR)
7748                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7749         if(!loadFlag) {
7750             fprintf(serverMoves, "/%d/%d",
7751                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7752             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7753             else                      timeLeft = blackTimeRemaining/1000;
7754             fprintf(serverMoves, "/%d", timeLeft);
7755         }
7756         fflush(serverMoves);
7757     }
7758
7759     if (forwardMostMove+1 >= MAX_MOVES) {
7760       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7761                         0, 1);
7762       return;
7763     }
7764     if (commentList[forwardMostMove+1] != NULL) {
7765         free(commentList[forwardMostMove+1]);
7766         commentList[forwardMostMove+1] = NULL;
7767     }
7768     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7769     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7770     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7771                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7772     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7773     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
7774     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7775     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7776     gameInfo.result = GameUnfinished;
7777     if (gameInfo.resultDetails != NULL) {
7778         free(gameInfo.resultDetails);
7779         gameInfo.resultDetails = NULL;
7780     }
7781     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7782                               moveList[forwardMostMove - 1]);
7783     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7784                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7785                              fromY, fromX, toY, toX, promoChar,
7786                              parseList[forwardMostMove - 1]);
7787     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7788                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7789                             castlingRights[forwardMostMove]) ) {
7790       case MT_NONE:
7791       case MT_STALEMATE:
7792       default:
7793         break;
7794       case MT_CHECK:
7795         if(gameInfo.variant != VariantShogi)
7796             strcat(parseList[forwardMostMove - 1], "+");
7797         break;
7798       case MT_CHECKMATE:
7799       case MT_STAINMATE:
7800         strcat(parseList[forwardMostMove - 1], "#");
7801         break;
7802     }
7803     if (appData.debugMode) {
7804         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7805     }
7806
7807 }
7808
7809 /* Updates currentMove if not pausing */
7810 void
7811 ShowMove(fromX, fromY, toX, toY)
7812 {
7813     int instant = (gameMode == PlayFromGameFile) ?
7814         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7815     if(appData.noGUI) return;
7816     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7817         if (!instant) {
7818             if (forwardMostMove == currentMove + 1) {
7819                 AnimateMove(boards[forwardMostMove - 1],
7820                             fromX, fromY, toX, toY);
7821             }
7822             if (appData.highlightLastMove) {
7823                 SetHighlights(fromX, fromY, toX, toY);
7824             }
7825         }
7826         currentMove = forwardMostMove;
7827     }
7828
7829     if (instant) return;
7830
7831     DisplayMove(currentMove - 1);
7832     DrawPosition(FALSE, boards[currentMove]);
7833     DisplayBothClocks();
7834     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7835 }
7836
7837 void SendEgtPath(ChessProgramState *cps)
7838 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7839         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7840
7841         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7842
7843         while(*p) {
7844             char c, *q = name+1, *r, *s;
7845
7846             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7847             while(*p && *p != ',') *q++ = *p++;
7848             *q++ = ':'; *q = 0;
7849             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7850                 strcmp(name, ",nalimov:") == 0 ) {
7851                 // take nalimov path from the menu-changeable option first, if it is defined
7852                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7853                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7854             } else
7855             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7856                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7857                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7858                 s = r = StrStr(s, ":") + 1; // beginning of path info
7859                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7860                 c = *r; *r = 0;             // temporarily null-terminate path info
7861                     *--q = 0;               // strip of trailig ':' from name
7862                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7863                 *r = c;
7864                 SendToProgram(buf,cps);     // send egtbpath command for this format
7865             }
7866             if(*p == ',') p++; // read away comma to position for next format name
7867         }
7868 }
7869
7870 void
7871 InitChessProgram(cps, setup)
7872      ChessProgramState *cps;
7873      int setup; /* [HGM] needed to setup FRC opening position */
7874 {
7875     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7876     if (appData.noChessProgram) return;
7877     hintRequested = FALSE;
7878     bookRequested = FALSE;
7879
7880     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7881     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7882     if(cps->memSize) { /* [HGM] memory */
7883         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7884         SendToProgram(buf, cps);
7885     }
7886     SendEgtPath(cps); /* [HGM] EGT */
7887     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7888         sprintf(buf, "cores %d\n", appData.smpCores);
7889         SendToProgram(buf, cps);
7890     }
7891
7892     SendToProgram(cps->initString, cps);
7893     if (gameInfo.variant != VariantNormal &&
7894         gameInfo.variant != VariantLoadable
7895         /* [HGM] also send variant if board size non-standard */
7896         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7897                                             ) {
7898       char *v = VariantName(gameInfo.variant);
7899       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7900         /* [HGM] in protocol 1 we have to assume all variants valid */
7901         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7902         DisplayFatalError(buf, 0, 1);
7903         return;
7904       }
7905
7906       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7907       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7908       if( gameInfo.variant == VariantXiangqi )
7909            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7910       if( gameInfo.variant == VariantShogi )
7911            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7912       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7913            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7914       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7915                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7916            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7917       if( gameInfo.variant == VariantCourier )
7918            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7919       if( gameInfo.variant == VariantSuper )
7920            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7921       if( gameInfo.variant == VariantGreat )
7922            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7923
7924       if(overruled) {
7925            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7926                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7927            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7928            if(StrStr(cps->variants, b) == NULL) { 
7929                // specific sized variant not known, check if general sizing allowed
7930                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7931                    if(StrStr(cps->variants, "boardsize") == NULL) {
7932                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7933                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7934                        DisplayFatalError(buf, 0, 1);
7935                        return;
7936                    }
7937                    /* [HGM] here we really should compare with the maximum supported board size */
7938                }
7939            }
7940       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7941       sprintf(buf, "variant %s\n", b);
7942       SendToProgram(buf, cps);
7943     }
7944     currentlyInitializedVariant = gameInfo.variant;
7945
7946     /* [HGM] send opening position in FRC to first engine */
7947     if(setup) {
7948           SendToProgram("force\n", cps);
7949           SendBoard(cps, 0);
7950           /* engine is now in force mode! Set flag to wake it up after first move. */
7951           setboardSpoiledMachineBlack = 1;
7952     }
7953
7954     if (cps->sendICS) {
7955       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7956       SendToProgram(buf, cps);
7957     }
7958     cps->maybeThinking = FALSE;
7959     cps->offeredDraw = 0;
7960     if (!appData.icsActive) {
7961         SendTimeControl(cps, movesPerSession, timeControl,
7962                         timeIncrement, appData.searchDepth,
7963                         searchTime);
7964     }
7965     if (appData.showThinking 
7966         // [HGM] thinking: four options require thinking output to be sent
7967         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7968                                 ) {
7969         SendToProgram("post\n", cps);
7970     }
7971     SendToProgram("hard\n", cps);
7972     if (!appData.ponderNextMove) {
7973         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7974            it without being sure what state we are in first.  "hard"
7975            is not a toggle, so that one is OK.
7976          */
7977         SendToProgram("easy\n", cps);
7978     }
7979     if (cps->usePing) {
7980       sprintf(buf, "ping %d\n", ++cps->lastPing);
7981       SendToProgram(buf, cps);
7982     }
7983     cps->initDone = TRUE;
7984 }   
7985
7986
7987 void
7988 StartChessProgram(cps)
7989      ChessProgramState *cps;
7990 {
7991     char buf[MSG_SIZ];
7992     int err;
7993
7994     if (appData.noChessProgram) return;
7995     cps->initDone = FALSE;
7996
7997     if (strcmp(cps->host, "localhost") == 0) {
7998         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7999     } else if (*appData.remoteShell == NULLCHAR) {
8000         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8001     } else {
8002         if (*appData.remoteUser == NULLCHAR) {
8003           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8004                     cps->program);
8005         } else {
8006           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8007                     cps->host, appData.remoteUser, cps->program);
8008         }
8009         err = StartChildProcess(buf, "", &cps->pr);
8010     }
8011     
8012     if (err != 0) {
8013         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8014         DisplayFatalError(buf, err, 1);
8015         cps->pr = NoProc;
8016         cps->isr = NULL;
8017         return;
8018     }
8019     
8020     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8021     if (cps->protocolVersion > 1) {
8022       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8023       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8024       cps->comboCnt = 0;  //                and values of combo boxes
8025       SendToProgram(buf, cps);
8026     } else {
8027       SendToProgram("xboard\n", cps);
8028     }
8029 }
8030
8031
8032 void
8033 TwoMachinesEventIfReady P((void))
8034 {
8035   if (first.lastPing != first.lastPong) {
8036     DisplayMessage("", _("Waiting for first chess program"));
8037     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8038     return;
8039   }
8040   if (second.lastPing != second.lastPong) {
8041     DisplayMessage("", _("Waiting for second chess program"));
8042     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8043     return;
8044   }
8045   ThawUI();
8046   TwoMachinesEvent();
8047 }
8048
8049 void
8050 NextMatchGame P((void))
8051 {
8052     int index; /* [HGM] autoinc: step load index during match */
8053     Reset(FALSE, TRUE);
8054     if (*appData.loadGameFile != NULLCHAR) {
8055         index = appData.loadGameIndex;
8056         if(index < 0) { // [HGM] autoinc
8057             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8058             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8059         } 
8060         LoadGameFromFile(appData.loadGameFile,
8061                          index,
8062                          appData.loadGameFile, FALSE);
8063     } else if (*appData.loadPositionFile != NULLCHAR) {
8064         index = appData.loadPositionIndex;
8065         if(index < 0) { // [HGM] autoinc
8066             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8067             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8068         } 
8069         LoadPositionFromFile(appData.loadPositionFile,
8070                              index,
8071                              appData.loadPositionFile);
8072     }
8073     TwoMachinesEventIfReady();
8074 }
8075
8076 void UserAdjudicationEvent( int result )
8077 {
8078     ChessMove gameResult = GameIsDrawn;
8079
8080     if( result > 0 ) {
8081         gameResult = WhiteWins;
8082     }
8083     else if( result < 0 ) {
8084         gameResult = BlackWins;
8085     }
8086
8087     if( gameMode == TwoMachinesPlay ) {
8088         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8089     }
8090 }
8091
8092
8093 // [HGM] save: calculate checksum of game to make games easily identifiable
8094 int StringCheckSum(char *s)
8095 {
8096         int i = 0;
8097         if(s==NULL) return 0;
8098         while(*s) i = i*259 + *s++;
8099         return i;
8100 }
8101
8102 int GameCheckSum()
8103 {
8104         int i, sum=0;
8105         for(i=backwardMostMove; i<forwardMostMove; i++) {
8106                 sum += pvInfoList[i].depth;
8107                 sum += StringCheckSum(parseList[i]);
8108                 sum += StringCheckSum(commentList[i]);
8109                 sum *= 261;
8110         }
8111         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8112         return sum + StringCheckSum(commentList[i]);
8113 } // end of save patch
8114
8115 void
8116 GameEnds(result, resultDetails, whosays)
8117      ChessMove result;
8118      char *resultDetails;
8119      int whosays;
8120 {
8121     GameMode nextGameMode;
8122     int isIcsGame;
8123     char buf[MSG_SIZ];
8124
8125     if(endingGame) return; /* [HGM] crash: forbid recursion */
8126     endingGame = 1;
8127
8128     if (appData.debugMode) {
8129       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8130               result, resultDetails ? resultDetails : "(null)", whosays);
8131     }
8132
8133     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8134
8135     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8136         /* If we are playing on ICS, the server decides when the
8137            game is over, but the engine can offer to draw, claim 
8138            a draw, or resign. 
8139          */
8140 #if ZIPPY
8141         if (appData.zippyPlay && first.initDone) {
8142             if (result == GameIsDrawn) {
8143                 /* In case draw still needs to be claimed */
8144                 SendToICS(ics_prefix);
8145                 SendToICS("draw\n");
8146             } else if (StrCaseStr(resultDetails, "resign")) {
8147                 SendToICS(ics_prefix);
8148                 SendToICS("resign\n");
8149             }
8150         }
8151 #endif
8152         endingGame = 0; /* [HGM] crash */
8153         return;
8154     }
8155
8156     /* If we're loading the game from a file, stop */
8157     if (whosays == GE_FILE) {
8158       (void) StopLoadGameTimer();
8159       gameFileFP = NULL;
8160     }
8161
8162     /* Cancel draw offers */
8163     first.offeredDraw = second.offeredDraw = 0;
8164
8165     /* If this is an ICS game, only ICS can really say it's done;
8166        if not, anyone can. */
8167     isIcsGame = (gameMode == IcsPlayingWhite || 
8168                  gameMode == IcsPlayingBlack || 
8169                  gameMode == IcsObserving    || 
8170                  gameMode == IcsExamining);
8171
8172     if (!isIcsGame || whosays == GE_ICS) {
8173         /* OK -- not an ICS game, or ICS said it was done */
8174         StopClocks();
8175         if (!isIcsGame && !appData.noChessProgram) 
8176           SetUserThinkingEnables();
8177     
8178         /* [HGM] if a machine claims the game end we verify this claim */
8179         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8180             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8181                 char claimer;
8182                 ChessMove trueResult = (ChessMove) -1;
8183
8184                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8185                                             first.twoMachinesColor[0] :
8186                                             second.twoMachinesColor[0] ;
8187
8188                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8189                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8190                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8191                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8192                 } else
8193                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8194                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8195                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8196                 } else
8197                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8198                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8199                 }
8200
8201                 // now verify win claims, but not in drop games, as we don't understand those yet
8202                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8203                                                  || gameInfo.variant == VariantGreat) &&
8204                     (result == WhiteWins && claimer == 'w' ||
8205                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8206                       if (appData.debugMode) {
8207                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8208                                 result, epStatus[forwardMostMove], forwardMostMove);
8209                       }
8210                       if(result != trueResult) {
8211                               sprintf(buf, "False win claim: '%s'", resultDetails);
8212                               result = claimer == 'w' ? BlackWins : WhiteWins;
8213                               resultDetails = buf;
8214                       }
8215                 } else
8216                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8217                     && (forwardMostMove <= backwardMostMove ||
8218                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8219                         (claimer=='b')==(forwardMostMove&1))
8220                                                                                   ) {
8221                       /* [HGM] verify: draws that were not flagged are false claims */
8222                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8223                       result = claimer == 'w' ? BlackWins : WhiteWins;
8224                       resultDetails = buf;
8225                 }
8226                 /* (Claiming a loss is accepted no questions asked!) */
8227             }
8228             /* [HGM] bare: don't allow bare King to win */
8229             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8230                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8231                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8232                && result != GameIsDrawn)
8233             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8234                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8235                         int p = (int)boards[forwardMostMove][i][j] - color;
8236                         if(p >= 0 && p <= (int)WhiteKing) k++;
8237                 }
8238                 if (appData.debugMode) {
8239                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8240                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8241                 }
8242                 if(k <= 1) {
8243                         result = GameIsDrawn;
8244                         sprintf(buf, "%s but bare king", resultDetails);
8245                         resultDetails = buf;
8246                 }
8247             }
8248         }
8249
8250
8251         if(serverMoves != NULL && !loadFlag) { char c = '=';
8252             if(result==WhiteWins) c = '+';
8253             if(result==BlackWins) c = '-';
8254             if(resultDetails != NULL)
8255                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8256         }
8257         if (resultDetails != NULL) {
8258             gameInfo.result = result;
8259             gameInfo.resultDetails = StrSave(resultDetails);
8260
8261             /* display last move only if game was not loaded from file */
8262             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8263                 DisplayMove(currentMove - 1);
8264     
8265             if (forwardMostMove != 0) {
8266                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8267                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8268                                                                 ) {
8269                     if (*appData.saveGameFile != NULLCHAR) {
8270                         SaveGameToFile(appData.saveGameFile, TRUE);
8271                     } else if (appData.autoSaveGames) {
8272                         AutoSaveGame();
8273                     }
8274                     if (*appData.savePositionFile != NULLCHAR) {
8275                         SavePositionToFile(appData.savePositionFile);
8276                     }
8277                 }
8278             }
8279
8280             /* Tell program how game ended in case it is learning */
8281             /* [HGM] Moved this to after saving the PGN, just in case */
8282             /* engine died and we got here through time loss. In that */
8283             /* case we will get a fatal error writing the pipe, which */
8284             /* would otherwise lose us the PGN.                       */
8285             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8286             /* output during GameEnds should never be fatal anymore   */
8287             if (gameMode == MachinePlaysWhite ||
8288                 gameMode == MachinePlaysBlack ||
8289                 gameMode == TwoMachinesPlay ||
8290                 gameMode == IcsPlayingWhite ||
8291                 gameMode == IcsPlayingBlack ||
8292                 gameMode == BeginningOfGame) {
8293                 char buf[MSG_SIZ];
8294                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8295                         resultDetails);
8296                 if (first.pr != NoProc) {
8297                     SendToProgram(buf, &first);
8298                 }
8299                 if (second.pr != NoProc &&
8300                     gameMode == TwoMachinesPlay) {
8301                     SendToProgram(buf, &second);
8302                 }
8303             }
8304         }
8305
8306         if (appData.icsActive) {
8307             if (appData.quietPlay &&
8308                 (gameMode == IcsPlayingWhite ||
8309                  gameMode == IcsPlayingBlack)) {
8310                 SendToICS(ics_prefix);
8311                 SendToICS("set shout 1\n");
8312             }
8313             nextGameMode = IcsIdle;
8314             ics_user_moved = FALSE;
8315             /* clean up premove.  It's ugly when the game has ended and the
8316              * premove highlights are still on the board.
8317              */
8318             if (gotPremove) {
8319               gotPremove = FALSE;
8320               ClearPremoveHighlights();
8321               DrawPosition(FALSE, boards[currentMove]);
8322             }
8323             if (whosays == GE_ICS) {
8324                 switch (result) {
8325                 case WhiteWins:
8326                     if (gameMode == IcsPlayingWhite)
8327                         PlayIcsWinSound();
8328                     else if(gameMode == IcsPlayingBlack)
8329                         PlayIcsLossSound();
8330                     break;
8331                 case BlackWins:
8332                     if (gameMode == IcsPlayingBlack)
8333                         PlayIcsWinSound();
8334                     else if(gameMode == IcsPlayingWhite)
8335                         PlayIcsLossSound();
8336                     break;
8337                 case GameIsDrawn:
8338                     PlayIcsDrawSound();
8339                     break;
8340                 default:
8341                     PlayIcsUnfinishedSound();
8342                 }
8343             }
8344         } else if (gameMode == EditGame ||
8345                    gameMode == PlayFromGameFile || 
8346                    gameMode == AnalyzeMode || 
8347                    gameMode == AnalyzeFile) {
8348             nextGameMode = gameMode;
8349         } else {
8350             nextGameMode = EndOfGame;
8351         }
8352         pausing = FALSE;
8353         ModeHighlight();
8354     } else {
8355         nextGameMode = gameMode;
8356     }
8357
8358     if (appData.noChessProgram) {
8359         gameMode = nextGameMode;
8360         ModeHighlight();
8361         endingGame = 0; /* [HGM] crash */
8362         return;
8363     }
8364
8365     if (first.reuse) {
8366         /* Put first chess program into idle state */
8367         if (first.pr != NoProc &&
8368             (gameMode == MachinePlaysWhite ||
8369              gameMode == MachinePlaysBlack ||
8370              gameMode == TwoMachinesPlay ||
8371              gameMode == IcsPlayingWhite ||
8372              gameMode == IcsPlayingBlack ||
8373              gameMode == BeginningOfGame)) {
8374             SendToProgram("force\n", &first);
8375             if (first.usePing) {
8376               char buf[MSG_SIZ];
8377               sprintf(buf, "ping %d\n", ++first.lastPing);
8378               SendToProgram(buf, &first);
8379             }
8380         }
8381     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8382         /* Kill off first chess program */
8383         if (first.isr != NULL)
8384           RemoveInputSource(first.isr);
8385         first.isr = NULL;
8386     
8387         if (first.pr != NoProc) {
8388             ExitAnalyzeMode();
8389             DoSleep( appData.delayBeforeQuit );
8390             SendToProgram("quit\n", &first);
8391             DoSleep( appData.delayAfterQuit );
8392             DestroyChildProcess(first.pr, first.useSigterm);
8393         }
8394         first.pr = NoProc;
8395     }
8396     if (second.reuse) {
8397         /* Put second chess program into idle state */
8398         if (second.pr != NoProc &&
8399             gameMode == TwoMachinesPlay) {
8400             SendToProgram("force\n", &second);
8401             if (second.usePing) {
8402               char buf[MSG_SIZ];
8403               sprintf(buf, "ping %d\n", ++second.lastPing);
8404               SendToProgram(buf, &second);
8405             }
8406         }
8407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8408         /* Kill off second chess program */
8409         if (second.isr != NULL)
8410           RemoveInputSource(second.isr);
8411         second.isr = NULL;
8412     
8413         if (second.pr != NoProc) {
8414             DoSleep( appData.delayBeforeQuit );
8415             SendToProgram("quit\n", &second);
8416             DoSleep( appData.delayAfterQuit );
8417             DestroyChildProcess(second.pr, second.useSigterm);
8418         }
8419         second.pr = NoProc;
8420     }
8421
8422     if (matchMode && gameMode == TwoMachinesPlay) {
8423         switch (result) {
8424         case WhiteWins:
8425           if (first.twoMachinesColor[0] == 'w') {
8426             first.matchWins++;
8427           } else {
8428             second.matchWins++;
8429           }
8430           break;
8431         case BlackWins:
8432           if (first.twoMachinesColor[0] == 'b') {
8433             first.matchWins++;
8434           } else {
8435             second.matchWins++;
8436           }
8437           break;
8438         default:
8439           break;
8440         }
8441         if (matchGame < appData.matchGames) {
8442             char *tmp;
8443             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8444                 tmp = first.twoMachinesColor;
8445                 first.twoMachinesColor = second.twoMachinesColor;
8446                 second.twoMachinesColor = tmp;
8447             }
8448             gameMode = nextGameMode;
8449             matchGame++;
8450             if(appData.matchPause>10000 || appData.matchPause<10)
8451                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8452             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8453             endingGame = 0; /* [HGM] crash */
8454             return;
8455         } else {
8456             char buf[MSG_SIZ];
8457             gameMode = nextGameMode;
8458             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8459                     first.tidy, second.tidy,
8460                     first.matchWins, second.matchWins,
8461                     appData.matchGames - (first.matchWins + second.matchWins));
8462             DisplayFatalError(buf, 0, 0);
8463         }
8464     }
8465     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8466         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8467       ExitAnalyzeMode();
8468     gameMode = nextGameMode;
8469     ModeHighlight();
8470     endingGame = 0;  /* [HGM] crash */
8471 }
8472
8473 /* Assumes program was just initialized (initString sent).
8474    Leaves program in force mode. */
8475 void
8476 FeedMovesToProgram(cps, upto) 
8477      ChessProgramState *cps;
8478      int upto;
8479 {
8480     int i;
8481     
8482     if (appData.debugMode)
8483       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8484               startedFromSetupPosition ? "position and " : "",
8485               backwardMostMove, upto, cps->which);
8486     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8487         // [HGM] variantswitch: make engine aware of new variant
8488         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8489                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8490         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8491         SendToProgram(buf, cps);
8492         currentlyInitializedVariant = gameInfo.variant;
8493     }
8494     SendToProgram("force\n", cps);
8495     if (startedFromSetupPosition) {
8496         SendBoard(cps, backwardMostMove);
8497     if (appData.debugMode) {
8498         fprintf(debugFP, "feedMoves\n");
8499     }
8500     }
8501     for (i = backwardMostMove; i < upto; i++) {
8502         SendMoveToProgram(i, cps);
8503     }
8504 }
8505
8506
8507 void
8508 ResurrectChessProgram()
8509 {
8510      /* The chess program may have exited.
8511         If so, restart it and feed it all the moves made so far. */
8512
8513     if (appData.noChessProgram || first.pr != NoProc) return;
8514     
8515     StartChessProgram(&first);
8516     InitChessProgram(&first, FALSE);
8517     FeedMovesToProgram(&first, currentMove);
8518
8519     if (!first.sendTime) {
8520         /* can't tell gnuchess what its clock should read,
8521            so we bow to its notion. */
8522         ResetClocks();
8523         timeRemaining[0][currentMove] = whiteTimeRemaining;
8524         timeRemaining[1][currentMove] = blackTimeRemaining;
8525     }
8526
8527     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8528                 appData.icsEngineAnalyze) && first.analysisSupport) {
8529       SendToProgram("analyze\n", &first);
8530       first.analyzing = TRUE;
8531     }
8532 }
8533
8534 /*
8535  * Button procedures
8536  */
8537 void
8538 Reset(redraw, init)
8539      int redraw, init;
8540 {
8541     int i;
8542
8543     if (appData.debugMode) {
8544         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8545                 redraw, init, gameMode);
8546     }
8547     pausing = pauseExamInvalid = FALSE;
8548     startedFromSetupPosition = blackPlaysFirst = FALSE;
8549     firstMove = TRUE;
8550     whiteFlag = blackFlag = FALSE;
8551     userOfferedDraw = FALSE;
8552     hintRequested = bookRequested = FALSE;
8553     first.maybeThinking = FALSE;
8554     second.maybeThinking = FALSE;
8555     first.bookSuspend = FALSE; // [HGM] book
8556     second.bookSuspend = FALSE;
8557     thinkOutput[0] = NULLCHAR;
8558     lastHint[0] = NULLCHAR;
8559     ClearGameInfo(&gameInfo);
8560     gameInfo.variant = StringToVariant(appData.variant);
8561     ics_user_moved = ics_clock_paused = FALSE;
8562     ics_getting_history = H_FALSE;
8563     ics_gamenum = -1;
8564     white_holding[0] = black_holding[0] = NULLCHAR;
8565     ClearProgramStats();
8566     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8567     
8568     ResetFrontEnd();
8569     ClearHighlights();
8570     flipView = appData.flipView;
8571     ClearPremoveHighlights();
8572     gotPremove = FALSE;
8573     alarmSounded = FALSE;
8574
8575     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8576     if(appData.serverMovesName != NULL) {
8577         /* [HGM] prepare to make moves file for broadcasting */
8578         clock_t t = clock();
8579         if(serverMoves != NULL) fclose(serverMoves);
8580         serverMoves = fopen(appData.serverMovesName, "r");
8581         if(serverMoves != NULL) {
8582             fclose(serverMoves);
8583             /* delay 15 sec before overwriting, so all clients can see end */
8584             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8585         }
8586         serverMoves = fopen(appData.serverMovesName, "w");
8587     }
8588
8589     ExitAnalyzeMode();
8590     gameMode = BeginningOfGame;
8591     ModeHighlight();
8592     if(appData.icsActive) gameInfo.variant = VariantNormal;
8593     currentMove = forwardMostMove = backwardMostMove = 0;
8594     InitPosition(redraw);
8595     for (i = 0; i < MAX_MOVES; i++) {
8596         if (commentList[i] != NULL) {
8597             free(commentList[i]);
8598             commentList[i] = NULL;
8599         }
8600     }
8601     ResetClocks();
8602     timeRemaining[0][0] = whiteTimeRemaining;
8603     timeRemaining[1][0] = blackTimeRemaining;
8604     if (first.pr == NULL) {
8605         StartChessProgram(&first);
8606     }
8607     if (init) {
8608             InitChessProgram(&first, startedFromSetupPosition);
8609     }
8610     DisplayTitle("");
8611     DisplayMessage("", "");
8612     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8613     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8614 }
8615
8616 void
8617 AutoPlayGameLoop()
8618 {
8619     for (;;) {
8620         if (!AutoPlayOneMove())
8621           return;
8622         if (matchMode || appData.timeDelay == 0)
8623           continue;
8624         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8625           return;
8626         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8627         break;
8628     }
8629 }
8630
8631
8632 int
8633 AutoPlayOneMove()
8634 {
8635     int fromX, fromY, toX, toY;
8636
8637     if (appData.debugMode) {
8638       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8639     }
8640
8641     if (gameMode != PlayFromGameFile)
8642       return FALSE;
8643
8644     if (currentMove >= forwardMostMove) {
8645       gameMode = EditGame;
8646       ModeHighlight();
8647
8648       /* [AS] Clear current move marker at the end of a game */
8649       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8650
8651       return FALSE;
8652     }
8653     
8654     toX = moveList[currentMove][2] - AAA;
8655     toY = moveList[currentMove][3] - ONE;
8656
8657     if (moveList[currentMove][1] == '@') {
8658         if (appData.highlightLastMove) {
8659             SetHighlights(-1, -1, toX, toY);
8660         }
8661     } else {
8662         fromX = moveList[currentMove][0] - AAA;
8663         fromY = moveList[currentMove][1] - ONE;
8664
8665         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8666
8667         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8668
8669         if (appData.highlightLastMove) {
8670             SetHighlights(fromX, fromY, toX, toY);
8671         }
8672     }
8673     DisplayMove(currentMove);
8674     SendMoveToProgram(currentMove++, &first);
8675     DisplayBothClocks();
8676     DrawPosition(FALSE, boards[currentMove]);
8677     // [HGM] PV info: always display, routine tests if empty
8678     DisplayComment(currentMove - 1, commentList[currentMove]);
8679     return TRUE;
8680 }
8681
8682
8683 int
8684 LoadGameOneMove(readAhead)
8685      ChessMove readAhead;
8686 {
8687     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8688     char promoChar = NULLCHAR;
8689     ChessMove moveType;
8690     char move[MSG_SIZ];
8691     char *p, *q;
8692     
8693     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8694         gameMode != AnalyzeMode && gameMode != Training) {
8695         gameFileFP = NULL;
8696         return FALSE;
8697     }
8698     
8699     yyboardindex = forwardMostMove;
8700     if (readAhead != (ChessMove)0) {
8701       moveType = readAhead;
8702     } else {
8703       if (gameFileFP == NULL)
8704           return FALSE;
8705       moveType = (ChessMove) yylex();
8706     }
8707     
8708     done = FALSE;
8709     switch (moveType) {
8710       case Comment:
8711         if (appData.debugMode) 
8712           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8713         p = yy_text;
8714         if (*p == '{' || *p == '[' || *p == '(') {
8715             p[strlen(p) - 1] = NULLCHAR;
8716             p++;
8717         }
8718
8719         /* append the comment but don't display it */
8720         while (*p == '\n') p++;
8721         AppendComment(currentMove, p);
8722         return TRUE;
8723
8724       case WhiteCapturesEnPassant:
8725       case BlackCapturesEnPassant:
8726       case WhitePromotionChancellor:
8727       case BlackPromotionChancellor:
8728       case WhitePromotionArchbishop:
8729       case BlackPromotionArchbishop:
8730       case WhitePromotionCentaur:
8731       case BlackPromotionCentaur:
8732       case WhitePromotionQueen:
8733       case BlackPromotionQueen:
8734       case WhitePromotionRook:
8735       case BlackPromotionRook:
8736       case WhitePromotionBishop:
8737       case BlackPromotionBishop:
8738       case WhitePromotionKnight:
8739       case BlackPromotionKnight:
8740       case WhitePromotionKing:
8741       case BlackPromotionKing:
8742       case NormalMove:
8743       case WhiteKingSideCastle:
8744       case WhiteQueenSideCastle:
8745       case BlackKingSideCastle:
8746       case BlackQueenSideCastle:
8747       case WhiteKingSideCastleWild:
8748       case WhiteQueenSideCastleWild:
8749       case BlackKingSideCastleWild:
8750       case BlackQueenSideCastleWild:
8751       /* PUSH Fabien */
8752       case WhiteHSideCastleFR:
8753       case WhiteASideCastleFR:
8754       case BlackHSideCastleFR:
8755       case BlackASideCastleFR:
8756       /* POP Fabien */
8757         if (appData.debugMode)
8758           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8759         fromX = currentMoveString[0] - AAA;
8760         fromY = currentMoveString[1] - ONE;
8761         toX = currentMoveString[2] - AAA;
8762         toY = currentMoveString[3] - ONE;
8763         promoChar = currentMoveString[4];
8764         break;
8765
8766       case WhiteDrop:
8767       case BlackDrop:
8768         if (appData.debugMode)
8769           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8770         fromX = moveType == WhiteDrop ?
8771           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8772         (int) CharToPiece(ToLower(currentMoveString[0]));
8773         fromY = DROP_RANK;
8774         toX = currentMoveString[2] - AAA;
8775         toY = currentMoveString[3] - ONE;
8776         break;
8777
8778       case WhiteWins:
8779       case BlackWins:
8780       case GameIsDrawn:
8781       case GameUnfinished:
8782         if (appData.debugMode)
8783           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8784         p = strchr(yy_text, '{');
8785         if (p == NULL) p = strchr(yy_text, '(');
8786         if (p == NULL) {
8787             p = yy_text;
8788             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8789         } else {
8790             q = strchr(p, *p == '{' ? '}' : ')');
8791             if (q != NULL) *q = NULLCHAR;
8792             p++;
8793         }
8794         GameEnds(moveType, p, GE_FILE);
8795         done = TRUE;
8796         if (cmailMsgLoaded) {
8797             ClearHighlights();
8798             flipView = WhiteOnMove(currentMove);
8799             if (moveType == GameUnfinished) flipView = !flipView;
8800             if (appData.debugMode)
8801               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8802         }
8803         break;
8804
8805       case (ChessMove) 0:       /* end of file */
8806         if (appData.debugMode)
8807           fprintf(debugFP, "Parser hit end of file\n");
8808         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8809                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8810           case MT_NONE:
8811           case MT_CHECK:
8812             break;
8813           case MT_CHECKMATE:
8814           case MT_STAINMATE:
8815             if (WhiteOnMove(currentMove)) {
8816                 GameEnds(BlackWins, "Black mates", GE_FILE);
8817             } else {
8818                 GameEnds(WhiteWins, "White mates", GE_FILE);
8819             }
8820             break;
8821           case MT_STALEMATE:
8822             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8823             break;
8824         }
8825         done = TRUE;
8826         break;
8827
8828       case MoveNumberOne:
8829         if (lastLoadGameStart == GNUChessGame) {
8830             /* GNUChessGames have numbers, but they aren't move numbers */
8831             if (appData.debugMode)
8832               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8833                       yy_text, (int) moveType);
8834             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8835         }
8836         /* else fall thru */
8837
8838       case XBoardGame:
8839       case GNUChessGame:
8840       case PGNTag:
8841         /* Reached start of next game in file */
8842         if (appData.debugMode)
8843           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8844         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8845                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8846           case MT_NONE:
8847           case MT_CHECK:
8848             break;
8849           case MT_CHECKMATE:
8850           case MT_STAINMATE:
8851             if (WhiteOnMove(currentMove)) {
8852                 GameEnds(BlackWins, "Black mates", GE_FILE);
8853             } else {
8854                 GameEnds(WhiteWins, "White mates", GE_FILE);
8855             }
8856             break;
8857           case MT_STALEMATE:
8858             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8859             break;
8860         }
8861         done = TRUE;
8862         break;
8863
8864       case PositionDiagram:     /* should not happen; ignore */
8865       case ElapsedTime:         /* ignore */
8866       case NAG:                 /* ignore */
8867         if (appData.debugMode)
8868           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8869                   yy_text, (int) moveType);
8870         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8871
8872       case IllegalMove:
8873         if (appData.testLegality) {
8874             if (appData.debugMode)
8875               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8876             sprintf(move, _("Illegal move: %d.%s%s"),
8877                     (forwardMostMove / 2) + 1,
8878                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8879             DisplayError(move, 0);
8880             done = TRUE;
8881         } else {
8882             if (appData.debugMode)
8883               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8884                       yy_text, currentMoveString);
8885             fromX = currentMoveString[0] - AAA;
8886             fromY = currentMoveString[1] - ONE;
8887             toX = currentMoveString[2] - AAA;
8888             toY = currentMoveString[3] - ONE;
8889             promoChar = currentMoveString[4];
8890         }
8891         break;
8892
8893       case AmbiguousMove:
8894         if (appData.debugMode)
8895           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8896         sprintf(move, _("Ambiguous move: %d.%s%s"),
8897                 (forwardMostMove / 2) + 1,
8898                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8899         DisplayError(move, 0);
8900         done = TRUE;
8901         break;
8902
8903       default:
8904       case ImpossibleMove:
8905         if (appData.debugMode)
8906           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8907         sprintf(move, _("Illegal move: %d.%s%s"),
8908                 (forwardMostMove / 2) + 1,
8909                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8910         DisplayError(move, 0);
8911         done = TRUE;
8912         break;
8913     }
8914
8915     if (done) {
8916         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8917             DrawPosition(FALSE, boards[currentMove]);
8918             DisplayBothClocks();
8919             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8920               DisplayComment(currentMove - 1, commentList[currentMove]);
8921         }
8922         (void) StopLoadGameTimer();
8923         gameFileFP = NULL;
8924         cmailOldMove = forwardMostMove;
8925         return FALSE;
8926     } else {
8927         /* currentMoveString is set as a side-effect of yylex */
8928         strcat(currentMoveString, "\n");
8929         strcpy(moveList[forwardMostMove], currentMoveString);
8930         
8931         thinkOutput[0] = NULLCHAR;
8932         MakeMove(fromX, fromY, toX, toY, promoChar);
8933         currentMove = forwardMostMove;
8934         return TRUE;
8935     }
8936 }
8937
8938 /* Load the nth game from the given file */
8939 int
8940 LoadGameFromFile(filename, n, title, useList)
8941      char *filename;
8942      int n;
8943      char *title;
8944      /*Boolean*/ int useList;
8945 {
8946     FILE *f;
8947     char buf[MSG_SIZ];
8948
8949     if (strcmp(filename, "-") == 0) {
8950         f = stdin;
8951         title = "stdin";
8952     } else {
8953         f = fopen(filename, "rb");
8954         if (f == NULL) {
8955           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8956             DisplayError(buf, errno);
8957             return FALSE;
8958         }
8959     }
8960     if (fseek(f, 0, 0) == -1) {
8961         /* f is not seekable; probably a pipe */
8962         useList = FALSE;
8963     }
8964     if (useList && n == 0) {
8965         int error = GameListBuild(f);
8966         if (error) {
8967             DisplayError(_("Cannot build game list"), error);
8968         } else if (!ListEmpty(&gameList) &&
8969                    ((ListGame *) gameList.tailPred)->number > 1) {
8970             GameListPopUp(f, title);
8971             return TRUE;
8972         }
8973         GameListDestroy();
8974         n = 1;
8975     }
8976     if (n == 0) n = 1;
8977     return LoadGame(f, n, title, FALSE);
8978 }
8979
8980
8981 void
8982 MakeRegisteredMove()
8983 {
8984     int fromX, fromY, toX, toY;
8985     char promoChar;
8986     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8987         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8988           case CMAIL_MOVE:
8989           case CMAIL_DRAW:
8990             if (appData.debugMode)
8991               fprintf(debugFP, "Restoring %s for game %d\n",
8992                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8993     
8994             thinkOutput[0] = NULLCHAR;
8995             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8996             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8997             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8998             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8999             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9000             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9001             MakeMove(fromX, fromY, toX, toY, promoChar);
9002             ShowMove(fromX, fromY, toX, toY);
9003               
9004             switch (MateTest(boards[currentMove], PosFlags(currentMove),
9005                              EP_UNKNOWN, castlingRights[currentMove]) ) {
9006               case MT_NONE:
9007               case MT_CHECK:
9008                 break;
9009                 
9010               case MT_CHECKMATE:
9011               case MT_STAINMATE:
9012                 if (WhiteOnMove(currentMove)) {
9013                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9014                 } else {
9015                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9016                 }
9017                 break;
9018                 
9019               case MT_STALEMATE:
9020                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9021                 break;
9022             }
9023
9024             break;
9025             
9026           case CMAIL_RESIGN:
9027             if (WhiteOnMove(currentMove)) {
9028                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9029             } else {
9030                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9031             }
9032             break;
9033             
9034           case CMAIL_ACCEPT:
9035             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9036             break;
9037               
9038           default:
9039             break;
9040         }
9041     }
9042
9043     return;
9044 }
9045
9046 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9047 int
9048 CmailLoadGame(f, gameNumber, title, useList)
9049      FILE *f;
9050      int gameNumber;
9051      char *title;
9052      int useList;
9053 {
9054     int retVal;
9055
9056     if (gameNumber > nCmailGames) {
9057         DisplayError(_("No more games in this message"), 0);
9058         return FALSE;
9059     }
9060     if (f == lastLoadGameFP) {
9061         int offset = gameNumber - lastLoadGameNumber;
9062         if (offset == 0) {
9063             cmailMsg[0] = NULLCHAR;
9064             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9065                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9066                 nCmailMovesRegistered--;
9067             }
9068             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9069             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9070                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9071             }
9072         } else {
9073             if (! RegisterMove()) return FALSE;
9074         }
9075     }
9076
9077     retVal = LoadGame(f, gameNumber, title, useList);
9078
9079     /* Make move registered during previous look at this game, if any */
9080     MakeRegisteredMove();
9081
9082     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9083         commentList[currentMove]
9084           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9085         DisplayComment(currentMove - 1, commentList[currentMove]);
9086     }
9087
9088     return retVal;
9089 }
9090
9091 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9092 int
9093 ReloadGame(offset)
9094      int offset;
9095 {
9096     int gameNumber = lastLoadGameNumber + offset;
9097     if (lastLoadGameFP == NULL) {
9098         DisplayError(_("No game has been loaded yet"), 0);
9099         return FALSE;
9100     }
9101     if (gameNumber <= 0) {
9102         DisplayError(_("Can't back up any further"), 0);
9103         return FALSE;
9104     }
9105     if (cmailMsgLoaded) {
9106         return CmailLoadGame(lastLoadGameFP, gameNumber,
9107                              lastLoadGameTitle, lastLoadGameUseList);
9108     } else {
9109         return LoadGame(lastLoadGameFP, gameNumber,
9110                         lastLoadGameTitle, lastLoadGameUseList);
9111     }
9112 }
9113
9114
9115
9116 /* Load the nth game from open file f */
9117 int
9118 LoadGame(f, gameNumber, title, useList)
9119      FILE *f;
9120      int gameNumber;
9121      char *title;
9122      int useList;
9123 {
9124     ChessMove cm;
9125     char buf[MSG_SIZ];
9126     int gn = gameNumber;
9127     ListGame *lg = NULL;
9128     int numPGNTags = 0;
9129     int err;
9130     GameMode oldGameMode;
9131     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9132
9133     if (appData.debugMode) 
9134         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9135
9136     if (gameMode == Training )
9137         SetTrainingModeOff();
9138
9139     oldGameMode = gameMode;
9140     if (gameMode != BeginningOfGame) {
9141       Reset(FALSE, TRUE);
9142     }
9143
9144     gameFileFP = f;
9145     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9146         fclose(lastLoadGameFP);
9147     }
9148
9149     if (useList) {
9150         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9151         
9152         if (lg) {
9153             fseek(f, lg->offset, 0);
9154             GameListHighlight(gameNumber);
9155             gn = 1;
9156         }
9157         else {
9158             DisplayError(_("Game number out of range"), 0);
9159             return FALSE;
9160         }
9161     } else {
9162         GameListDestroy();
9163         if (fseek(f, 0, 0) == -1) {
9164             if (f == lastLoadGameFP ?
9165                 gameNumber == lastLoadGameNumber + 1 :
9166                 gameNumber == 1) {
9167                 gn = 1;
9168             } else {
9169                 DisplayError(_("Can't seek on game file"), 0);
9170                 return FALSE;
9171             }
9172         }
9173     }
9174     lastLoadGameFP = f;
9175     lastLoadGameNumber = gameNumber;
9176     strcpy(lastLoadGameTitle, title);
9177     lastLoadGameUseList = useList;
9178
9179     yynewfile(f);
9180
9181     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9182       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9183                 lg->gameInfo.black);
9184             DisplayTitle(buf);
9185     } else if (*title != NULLCHAR) {
9186         if (gameNumber > 1) {
9187             sprintf(buf, "%s %d", title, gameNumber);
9188             DisplayTitle(buf);
9189         } else {
9190             DisplayTitle(title);
9191         }
9192     }
9193
9194     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9195         gameMode = PlayFromGameFile;
9196         ModeHighlight();
9197     }
9198
9199     currentMove = forwardMostMove = backwardMostMove = 0;
9200     CopyBoard(boards[0], initialPosition);
9201     StopClocks();
9202
9203     /*
9204      * Skip the first gn-1 games in the file.
9205      * Also skip over anything that precedes an identifiable 
9206      * start of game marker, to avoid being confused by 
9207      * garbage at the start of the file.  Currently 
9208      * recognized start of game markers are the move number "1",
9209      * the pattern "gnuchess .* game", the pattern
9210      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9211      * A game that starts with one of the latter two patterns
9212      * will also have a move number 1, possibly
9213      * following a position diagram.
9214      * 5-4-02: Let's try being more lenient and allowing a game to
9215      * start with an unnumbered move.  Does that break anything?
9216      */
9217     cm = lastLoadGameStart = (ChessMove) 0;
9218     while (gn > 0) {
9219         yyboardindex = forwardMostMove;
9220         cm = (ChessMove) yylex();
9221         switch (cm) {
9222           case (ChessMove) 0:
9223             if (cmailMsgLoaded) {
9224                 nCmailGames = CMAIL_MAX_GAMES - gn;
9225             } else {
9226                 Reset(TRUE, TRUE);
9227                 DisplayError(_("Game not found in file"), 0);
9228             }
9229             return FALSE;
9230
9231           case GNUChessGame:
9232           case XBoardGame:
9233             gn--;
9234             lastLoadGameStart = cm;
9235             break;
9236             
9237           case MoveNumberOne:
9238             switch (lastLoadGameStart) {
9239               case GNUChessGame:
9240               case XBoardGame:
9241               case PGNTag:
9242                 break;
9243               case MoveNumberOne:
9244               case (ChessMove) 0:
9245                 gn--;           /* count this game */
9246                 lastLoadGameStart = cm;
9247                 break;
9248               default:
9249                 /* impossible */
9250                 break;
9251             }
9252             break;
9253
9254           case PGNTag:
9255             switch (lastLoadGameStart) {
9256               case GNUChessGame:
9257               case PGNTag:
9258               case MoveNumberOne:
9259               case (ChessMove) 0:
9260                 gn--;           /* count this game */
9261                 lastLoadGameStart = cm;
9262                 break;
9263               case XBoardGame:
9264                 lastLoadGameStart = cm; /* game counted already */
9265                 break;
9266               default:
9267                 /* impossible */
9268                 break;
9269             }
9270             if (gn > 0) {
9271                 do {
9272                     yyboardindex = forwardMostMove;
9273                     cm = (ChessMove) yylex();
9274                 } while (cm == PGNTag || cm == Comment);
9275             }
9276             break;
9277
9278           case WhiteWins:
9279           case BlackWins:
9280           case GameIsDrawn:
9281             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9282                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9283                     != CMAIL_OLD_RESULT) {
9284                     nCmailResults ++ ;
9285                     cmailResult[  CMAIL_MAX_GAMES
9286                                 - gn - 1] = CMAIL_OLD_RESULT;
9287                 }
9288             }
9289             break;
9290
9291           case NormalMove:
9292             /* Only a NormalMove can be at the start of a game
9293              * without a position diagram. */
9294             if (lastLoadGameStart == (ChessMove) 0) {
9295               gn--;
9296               lastLoadGameStart = MoveNumberOne;
9297             }
9298             break;
9299
9300           default:
9301             break;
9302         }
9303     }
9304     
9305     if (appData.debugMode)
9306       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9307
9308     if (cm == XBoardGame) {
9309         /* Skip any header junk before position diagram and/or move 1 */
9310         for (;;) {
9311             yyboardindex = forwardMostMove;
9312             cm = (ChessMove) yylex();
9313
9314             if (cm == (ChessMove) 0 ||
9315                 cm == GNUChessGame || cm == XBoardGame) {
9316                 /* Empty game; pretend end-of-file and handle later */
9317                 cm = (ChessMove) 0;
9318                 break;
9319             }
9320
9321             if (cm == MoveNumberOne || cm == PositionDiagram ||
9322                 cm == PGNTag || cm == Comment)
9323               break;
9324         }
9325     } else if (cm == GNUChessGame) {
9326         if (gameInfo.event != NULL) {
9327             free(gameInfo.event);
9328         }
9329         gameInfo.event = StrSave(yy_text);
9330     }   
9331
9332     startedFromSetupPosition = FALSE;
9333     while (cm == PGNTag) {
9334         if (appData.debugMode) 
9335           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9336         err = ParsePGNTag(yy_text, &gameInfo);
9337         if (!err) numPGNTags++;
9338
9339         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9340         if(gameInfo.variant != oldVariant) {
9341             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9342             InitPosition(TRUE);
9343             oldVariant = gameInfo.variant;
9344             if (appData.debugMode) 
9345               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9346         }
9347
9348
9349         if (gameInfo.fen != NULL) {
9350           Board initial_position;
9351           startedFromSetupPosition = TRUE;
9352           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9353             Reset(TRUE, TRUE);
9354             DisplayError(_("Bad FEN position in file"), 0);
9355             return FALSE;
9356           }
9357           CopyBoard(boards[0], initial_position);
9358           if (blackPlaysFirst) {
9359             currentMove = forwardMostMove = backwardMostMove = 1;
9360             CopyBoard(boards[1], initial_position);
9361             strcpy(moveList[0], "");
9362             strcpy(parseList[0], "");
9363             timeRemaining[0][1] = whiteTimeRemaining;
9364             timeRemaining[1][1] = blackTimeRemaining;
9365             if (commentList[0] != NULL) {
9366               commentList[1] = commentList[0];
9367               commentList[0] = NULL;
9368             }
9369           } else {
9370             currentMove = forwardMostMove = backwardMostMove = 0;
9371           }
9372           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9373           {   int i;
9374               initialRulePlies = FENrulePlies;
9375               epStatus[forwardMostMove] = FENepStatus;
9376               for( i=0; i< nrCastlingRights; i++ )
9377                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9378           }
9379           yyboardindex = forwardMostMove;
9380           free(gameInfo.fen);
9381           gameInfo.fen = NULL;
9382         }
9383
9384         yyboardindex = forwardMostMove;
9385         cm = (ChessMove) yylex();
9386
9387         /* Handle comments interspersed among the tags */
9388         while (cm == Comment) {
9389             char *p;
9390             if (appData.debugMode) 
9391               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9392             p = yy_text;
9393             if (*p == '{' || *p == '[' || *p == '(') {
9394                 p[strlen(p) - 1] = NULLCHAR;
9395                 p++;
9396             }
9397             while (*p == '\n') p++;
9398             AppendComment(currentMove, p);
9399             yyboardindex = forwardMostMove;
9400             cm = (ChessMove) yylex();
9401         }
9402     }
9403
9404     /* don't rely on existence of Event tag since if game was
9405      * pasted from clipboard the Event tag may not exist
9406      */
9407     if (numPGNTags > 0){
9408         char *tags;
9409         if (gameInfo.variant == VariantNormal) {
9410           gameInfo.variant = StringToVariant(gameInfo.event);
9411         }
9412         if (!matchMode) {
9413           if( appData.autoDisplayTags ) {
9414             tags = PGNTags(&gameInfo);
9415             TagsPopUp(tags, CmailMsg());
9416             free(tags);
9417           }
9418         }
9419     } else {
9420         /* Make something up, but don't display it now */
9421         SetGameInfo();
9422         TagsPopDown();
9423     }
9424
9425     if (cm == PositionDiagram) {
9426         int i, j;
9427         char *p;
9428         Board initial_position;
9429
9430         if (appData.debugMode)
9431           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9432
9433         if (!startedFromSetupPosition) {
9434             p = yy_text;
9435             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9436               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9437                 switch (*p) {
9438                   case '[':
9439                   case '-':
9440                   case ' ':
9441                   case '\t':
9442                   case '\n':
9443                   case '\r':
9444                     break;
9445                   default:
9446                     initial_position[i][j++] = CharToPiece(*p);
9447                     break;
9448                 }
9449             while (*p == ' ' || *p == '\t' ||
9450                    *p == '\n' || *p == '\r') p++;
9451         
9452             if (strncmp(p, "black", strlen("black"))==0)
9453               blackPlaysFirst = TRUE;
9454             else
9455               blackPlaysFirst = FALSE;
9456             startedFromSetupPosition = TRUE;
9457         
9458             CopyBoard(boards[0], initial_position);
9459             if (blackPlaysFirst) {
9460                 currentMove = forwardMostMove = backwardMostMove = 1;
9461                 CopyBoard(boards[1], initial_position);
9462                 strcpy(moveList[0], "");
9463                 strcpy(parseList[0], "");
9464                 timeRemaining[0][1] = whiteTimeRemaining;
9465                 timeRemaining[1][1] = blackTimeRemaining;
9466                 if (commentList[0] != NULL) {
9467                     commentList[1] = commentList[0];
9468                     commentList[0] = NULL;
9469                 }
9470             } else {
9471                 currentMove = forwardMostMove = backwardMostMove = 0;
9472             }
9473         }
9474         yyboardindex = forwardMostMove;
9475         cm = (ChessMove) yylex();
9476     }
9477
9478     if (first.pr == NoProc) {
9479         StartChessProgram(&first);
9480     }
9481     InitChessProgram(&first, FALSE);
9482     SendToProgram("force\n", &first);
9483     if (startedFromSetupPosition) {
9484         SendBoard(&first, forwardMostMove);
9485     if (appData.debugMode) {
9486         fprintf(debugFP, "Load Game\n");
9487     }
9488         DisplayBothClocks();
9489     }      
9490
9491     /* [HGM] server: flag to write setup moves in broadcast file as one */
9492     loadFlag = appData.suppressLoadMoves;
9493
9494     while (cm == Comment) {
9495         char *p;
9496         if (appData.debugMode) 
9497           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9498         p = yy_text;
9499         if (*p == '{' || *p == '[' || *p == '(') {
9500             p[strlen(p) - 1] = NULLCHAR;
9501             p++;
9502         }
9503         while (*p == '\n') p++;
9504         AppendComment(currentMove, p);
9505         yyboardindex = forwardMostMove;
9506         cm = (ChessMove) yylex();
9507     }
9508
9509     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9510         cm == WhiteWins || cm == BlackWins ||
9511         cm == GameIsDrawn || cm == GameUnfinished) {
9512         DisplayMessage("", _("No moves in game"));
9513         if (cmailMsgLoaded) {
9514             if (appData.debugMode)
9515               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9516             ClearHighlights();
9517             flipView = FALSE;
9518         }
9519         DrawPosition(FALSE, boards[currentMove]);
9520         DisplayBothClocks();
9521         gameMode = EditGame;
9522         ModeHighlight();
9523         gameFileFP = NULL;
9524         cmailOldMove = 0;
9525         return TRUE;
9526     }
9527
9528     // [HGM] PV info: routine tests if comment empty
9529     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9530         DisplayComment(currentMove - 1, commentList[currentMove]);
9531     }
9532     if (!matchMode && appData.timeDelay != 0) 
9533       DrawPosition(FALSE, boards[currentMove]);
9534
9535     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9536       programStats.ok_to_send = 1;
9537     }
9538
9539     /* if the first token after the PGN tags is a move
9540      * and not move number 1, retrieve it from the parser 
9541      */
9542     if (cm != MoveNumberOne)
9543         LoadGameOneMove(cm);
9544
9545     /* load the remaining moves from the file */
9546     while (LoadGameOneMove((ChessMove)0)) {
9547       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9548       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9549     }
9550
9551     /* rewind to the start of the game */
9552     currentMove = backwardMostMove;
9553
9554     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9555
9556     if (oldGameMode == AnalyzeFile ||
9557         oldGameMode == AnalyzeMode) {
9558       AnalyzeFileEvent();
9559     }
9560
9561     if (matchMode || appData.timeDelay == 0) {
9562       ToEndEvent();
9563       gameMode = EditGame;
9564       ModeHighlight();
9565     } else if (appData.timeDelay > 0) {
9566       AutoPlayGameLoop();
9567     }
9568
9569     if (appData.debugMode) 
9570         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9571
9572     loadFlag = 0; /* [HGM] true game starts */
9573     return TRUE;
9574 }
9575
9576 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9577 int
9578 ReloadPosition(offset)
9579      int offset;
9580 {
9581     int positionNumber = lastLoadPositionNumber + offset;
9582     if (lastLoadPositionFP == NULL) {
9583         DisplayError(_("No position has been loaded yet"), 0);
9584         return FALSE;
9585     }
9586     if (positionNumber <= 0) {
9587         DisplayError(_("Can't back up any further"), 0);
9588         return FALSE;
9589     }
9590     return LoadPosition(lastLoadPositionFP, positionNumber,
9591                         lastLoadPositionTitle);
9592 }
9593
9594 /* Load the nth position from the given file */
9595 int
9596 LoadPositionFromFile(filename, n, title)
9597      char *filename;
9598      int n;
9599      char *title;
9600 {
9601     FILE *f;
9602     char buf[MSG_SIZ];
9603
9604     if (strcmp(filename, "-") == 0) {
9605         return LoadPosition(stdin, n, "stdin");
9606     } else {
9607         f = fopen(filename, "rb");
9608         if (f == NULL) {
9609             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9610             DisplayError(buf, errno);
9611             return FALSE;
9612         } else {
9613             return LoadPosition(f, n, title);
9614         }
9615     }
9616 }
9617
9618 /* Load the nth position from the given open file, and close it */
9619 int
9620 LoadPosition(f, positionNumber, title)
9621      FILE *f;
9622      int positionNumber;
9623      char *title;
9624 {
9625     char *p, line[MSG_SIZ];
9626     Board initial_position;
9627     int i, j, fenMode, pn;
9628     
9629     if (gameMode == Training )
9630         SetTrainingModeOff();
9631
9632     if (gameMode != BeginningOfGame) {
9633         Reset(FALSE, TRUE);
9634     }
9635     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9636         fclose(lastLoadPositionFP);
9637     }
9638     if (positionNumber == 0) positionNumber = 1;
9639     lastLoadPositionFP = f;
9640     lastLoadPositionNumber = positionNumber;
9641     strcpy(lastLoadPositionTitle, title);
9642     if (first.pr == NoProc) {
9643       StartChessProgram(&first);
9644       InitChessProgram(&first, FALSE);
9645     }    
9646     pn = positionNumber;
9647     if (positionNumber < 0) {
9648         /* Negative position number means to seek to that byte offset */
9649         if (fseek(f, -positionNumber, 0) == -1) {
9650             DisplayError(_("Can't seek on position file"), 0);
9651             return FALSE;
9652         };
9653         pn = 1;
9654     } else {
9655         if (fseek(f, 0, 0) == -1) {
9656             if (f == lastLoadPositionFP ?
9657                 positionNumber == lastLoadPositionNumber + 1 :
9658                 positionNumber == 1) {
9659                 pn = 1;
9660             } else {
9661                 DisplayError(_("Can't seek on position file"), 0);
9662                 return FALSE;
9663             }
9664         }
9665     }
9666     /* See if this file is FEN or old-style xboard */
9667     if (fgets(line, MSG_SIZ, f) == NULL) {
9668         DisplayError(_("Position not found in file"), 0);
9669         return FALSE;
9670     }
9671     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9672     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9673
9674     if (pn >= 2) {
9675         if (fenMode || line[0] == '#') pn--;
9676         while (pn > 0) {
9677             /* skip positions before number pn */
9678             if (fgets(line, MSG_SIZ, f) == NULL) {
9679                 Reset(TRUE, TRUE);
9680                 DisplayError(_("Position not found in file"), 0);
9681                 return FALSE;
9682             }
9683             if (fenMode || line[0] == '#') pn--;
9684         }
9685     }
9686
9687     if (fenMode) {
9688         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9689             DisplayError(_("Bad FEN position in file"), 0);
9690             return FALSE;
9691         }
9692     } else {
9693         (void) fgets(line, MSG_SIZ, f);
9694         (void) fgets(line, MSG_SIZ, f);
9695     
9696         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9697             (void) fgets(line, MSG_SIZ, f);
9698             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9699                 if (*p == ' ')
9700                   continue;
9701                 initial_position[i][j++] = CharToPiece(*p);
9702             }
9703         }
9704     
9705         blackPlaysFirst = FALSE;
9706         if (!feof(f)) {
9707             (void) fgets(line, MSG_SIZ, f);
9708             if (strncmp(line, "black", strlen("black"))==0)
9709               blackPlaysFirst = TRUE;
9710         }
9711     }
9712     startedFromSetupPosition = TRUE;
9713     
9714     SendToProgram("force\n", &first);
9715     CopyBoard(boards[0], initial_position);
9716     if (blackPlaysFirst) {
9717         currentMove = forwardMostMove = backwardMostMove = 1;
9718         strcpy(moveList[0], "");
9719         strcpy(parseList[0], "");
9720         CopyBoard(boards[1], initial_position);
9721         DisplayMessage("", _("Black to play"));
9722     } else {
9723         currentMove = forwardMostMove = backwardMostMove = 0;
9724         DisplayMessage("", _("White to play"));
9725     }
9726           /* [HGM] copy FEN attributes as well */
9727           {   int i;
9728               initialRulePlies = FENrulePlies;
9729               epStatus[forwardMostMove] = FENepStatus;
9730               for( i=0; i< nrCastlingRights; i++ )
9731                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9732           }
9733     SendBoard(&first, forwardMostMove);
9734     if (appData.debugMode) {
9735 int i, j;
9736   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9737   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9738         fprintf(debugFP, "Load Position\n");
9739     }
9740
9741     if (positionNumber > 1) {
9742         sprintf(line, "%s %d", title, positionNumber);
9743         DisplayTitle(line);
9744     } else {
9745         DisplayTitle(title);
9746     }
9747     gameMode = EditGame;
9748     ModeHighlight();
9749     ResetClocks();
9750     timeRemaining[0][1] = whiteTimeRemaining;
9751     timeRemaining[1][1] = blackTimeRemaining;
9752     DrawPosition(FALSE, boards[currentMove]);
9753    
9754     return TRUE;
9755 }
9756
9757
9758 void
9759 CopyPlayerNameIntoFileName(dest, src)
9760      char **dest, *src;
9761 {
9762     while (*src != NULLCHAR && *src != ',') {
9763         if (*src == ' ') {
9764             *(*dest)++ = '_';
9765             src++;
9766         } else {
9767             *(*dest)++ = *src++;
9768         }
9769     }
9770 }
9771
9772 char *DefaultFileName(ext)
9773      char *ext;
9774 {
9775     static char def[MSG_SIZ];
9776     char *p;
9777
9778     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9779         p = def;
9780         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9781         *p++ = '-';
9782         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9783         *p++ = '.';
9784         strcpy(p, ext);
9785     } else {
9786         def[0] = NULLCHAR;
9787     }
9788     return def;
9789 }
9790
9791 /* Save the current game to the given file */
9792 int
9793 SaveGameToFile(filename, append)
9794      char *filename;
9795      int append;
9796 {
9797     FILE *f;
9798     char buf[MSG_SIZ];
9799
9800     if (strcmp(filename, "-") == 0) {
9801         return SaveGame(stdout, 0, NULL);
9802     } else {
9803         f = fopen(filename, append ? "a" : "w");
9804         if (f == NULL) {
9805             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9806             DisplayError(buf, errno);
9807             return FALSE;
9808         } else {
9809             return SaveGame(f, 0, NULL);
9810         }
9811     }
9812 }
9813
9814 char *
9815 SavePart(str)
9816      char *str;
9817 {
9818     static char buf[MSG_SIZ];
9819     char *p;
9820     
9821     p = strchr(str, ' ');
9822     if (p == NULL) return str;
9823     strncpy(buf, str, p - str);
9824     buf[p - str] = NULLCHAR;
9825     return buf;
9826 }
9827
9828 #define PGN_MAX_LINE 75
9829
9830 #define PGN_SIDE_WHITE  0
9831 #define PGN_SIDE_BLACK  1
9832
9833 /* [AS] */
9834 static int FindFirstMoveOutOfBook( int side )
9835 {
9836     int result = -1;
9837
9838     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9839         int index = backwardMostMove;
9840         int has_book_hit = 0;
9841
9842         if( (index % 2) != side ) {
9843             index++;
9844         }
9845
9846         while( index < forwardMostMove ) {
9847             /* Check to see if engine is in book */
9848             int depth = pvInfoList[index].depth;
9849             int score = pvInfoList[index].score;
9850             int in_book = 0;
9851
9852             if( depth <= 2 ) {
9853                 in_book = 1;
9854             }
9855             else if( score == 0 && depth == 63 ) {
9856                 in_book = 1; /* Zappa */
9857             }
9858             else if( score == 2 && depth == 99 ) {
9859                 in_book = 1; /* Abrok */
9860             }
9861
9862             has_book_hit += in_book;
9863
9864             if( ! in_book ) {
9865                 result = index;
9866
9867                 break;
9868             }
9869
9870             index += 2;
9871         }
9872     }
9873
9874     return result;
9875 }
9876
9877 /* [AS] */
9878 void GetOutOfBookInfo( char * buf )
9879 {
9880     int oob[2];
9881     int i;
9882     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9883
9884     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9885     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9886
9887     *buf = '\0';
9888
9889     if( oob[0] >= 0 || oob[1] >= 0 ) {
9890         for( i=0; i<2; i++ ) {
9891             int idx = oob[i];
9892
9893             if( idx >= 0 ) {
9894                 if( i > 0 && oob[0] >= 0 ) {
9895                     strcat( buf, "   " );
9896                 }
9897
9898                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9899                 sprintf( buf+strlen(buf), "%s%.2f", 
9900                     pvInfoList[idx].score >= 0 ? "+" : "",
9901                     pvInfoList[idx].score / 100.0 );
9902             }
9903         }
9904     }
9905 }
9906
9907 /* Save game in PGN style and close the file */
9908 int
9909 SaveGamePGN(f)
9910      FILE *f;
9911 {
9912     int i, offset, linelen, newblock;
9913     time_t tm;
9914 //    char *movetext;
9915     char numtext[32];
9916     int movelen, numlen, blank;
9917     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9918
9919     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9920     
9921     tm = time((time_t *) NULL);
9922     
9923     PrintPGNTags(f, &gameInfo);
9924     
9925     if (backwardMostMove > 0 || startedFromSetupPosition) {
9926         char *fen = PositionToFEN(backwardMostMove, NULL);
9927         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9928         fprintf(f, "\n{--------------\n");
9929         PrintPosition(f, backwardMostMove);
9930         fprintf(f, "--------------}\n");
9931         free(fen);
9932     }
9933     else {
9934         /* [AS] Out of book annotation */
9935         if( appData.saveOutOfBookInfo ) {
9936             char buf[64];
9937
9938             GetOutOfBookInfo( buf );
9939
9940             if( buf[0] != '\0' ) {
9941                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9942             }
9943         }
9944
9945         fprintf(f, "\n");
9946     }
9947
9948     i = backwardMostMove;
9949     linelen = 0;
9950     newblock = TRUE;
9951
9952     while (i < forwardMostMove) {
9953         /* Print comments preceding this move */
9954         if (commentList[i] != NULL) {
9955             if (linelen > 0) fprintf(f, "\n");
9956             fprintf(f, "{\n%s}\n", commentList[i]);
9957             linelen = 0;
9958             newblock = TRUE;
9959         }
9960
9961         /* Format move number */
9962         if ((i % 2) == 0) {
9963             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9964         } else {
9965             if (newblock) {
9966                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9967             } else {
9968                 numtext[0] = NULLCHAR;
9969             }
9970         }
9971         numlen = strlen(numtext);
9972         newblock = FALSE;
9973
9974         /* Print move number */
9975         blank = linelen > 0 && numlen > 0;
9976         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9977             fprintf(f, "\n");
9978             linelen = 0;
9979             blank = 0;
9980         }
9981         if (blank) {
9982             fprintf(f, " ");
9983             linelen++;
9984         }
9985         fprintf(f, "%s", numtext);
9986         linelen += numlen;
9987
9988         /* Get move */
9989         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9990         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9991
9992         /* Print move */
9993         blank = linelen > 0 && movelen > 0;
9994         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9995             fprintf(f, "\n");
9996             linelen = 0;
9997             blank = 0;
9998         }
9999         if (blank) {
10000             fprintf(f, " ");
10001             linelen++;
10002         }
10003         fprintf(f, "%s", move_buffer);
10004         linelen += movelen;
10005
10006         /* [AS] Add PV info if present */
10007         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10008             /* [HGM] add time */
10009             char buf[MSG_SIZ]; int seconds;
10010
10011             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10012
10013             if( seconds <= 0) buf[0] = 0; else
10014             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10015                 seconds = (seconds + 4)/10; // round to full seconds
10016                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10017                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10018             }
10019
10020             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10021                 pvInfoList[i].score >= 0 ? "+" : "",
10022                 pvInfoList[i].score / 100.0,
10023                 pvInfoList[i].depth,
10024                 buf );
10025
10026             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10027
10028             /* Print score/depth */
10029             blank = linelen > 0 && movelen > 0;
10030             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10031                 fprintf(f, "\n");
10032                 linelen = 0;
10033                 blank = 0;
10034             }
10035             if (blank) {
10036                 fprintf(f, " ");
10037                 linelen++;
10038             }
10039             fprintf(f, "%s", move_buffer);
10040             linelen += movelen;
10041         }
10042
10043         i++;
10044     }
10045     
10046     /* Start a new line */
10047     if (linelen > 0) fprintf(f, "\n");
10048
10049     /* Print comments after last move */
10050     if (commentList[i] != NULL) {
10051         fprintf(f, "{\n%s}\n", commentList[i]);
10052     }
10053
10054     /* Print result */
10055     if (gameInfo.resultDetails != NULL &&
10056         gameInfo.resultDetails[0] != NULLCHAR) {
10057         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10058                 PGNResult(gameInfo.result));
10059     } else {
10060         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10061     }
10062
10063     fclose(f);
10064     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10065     return TRUE;
10066 }
10067
10068 /* Save game in old style and close the file */
10069 int
10070 SaveGameOldStyle(f)
10071      FILE *f;
10072 {
10073     int i, offset;
10074     time_t tm;
10075     
10076     tm = time((time_t *) NULL);
10077     
10078     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10079     PrintOpponents(f);
10080     
10081     if (backwardMostMove > 0 || startedFromSetupPosition) {
10082         fprintf(f, "\n[--------------\n");
10083         PrintPosition(f, backwardMostMove);
10084         fprintf(f, "--------------]\n");
10085     } else {
10086         fprintf(f, "\n");
10087     }
10088
10089     i = backwardMostMove;
10090     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10091
10092     while (i < forwardMostMove) {
10093         if (commentList[i] != NULL) {
10094             fprintf(f, "[%s]\n", commentList[i]);
10095         }
10096
10097         if ((i % 2) == 1) {
10098             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10099             i++;
10100         } else {
10101             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10102             i++;
10103             if (commentList[i] != NULL) {
10104                 fprintf(f, "\n");
10105                 continue;
10106             }
10107             if (i >= forwardMostMove) {
10108                 fprintf(f, "\n");
10109                 break;
10110             }
10111             fprintf(f, "%s\n", parseList[i]);
10112             i++;
10113         }
10114     }
10115     
10116     if (commentList[i] != NULL) {
10117         fprintf(f, "[%s]\n", commentList[i]);
10118     }
10119
10120     /* This isn't really the old style, but it's close enough */
10121     if (gameInfo.resultDetails != NULL &&
10122         gameInfo.resultDetails[0] != NULLCHAR) {
10123         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10124                 gameInfo.resultDetails);
10125     } else {
10126         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10127     }
10128
10129     fclose(f);
10130     return TRUE;
10131 }
10132
10133 /* Save the current game to open file f and close the file */
10134 int
10135 SaveGame(f, dummy, dummy2)
10136      FILE *f;
10137      int dummy;
10138      char *dummy2;
10139 {
10140     if (gameMode == EditPosition) EditPositionDone(TRUE);
10141     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10142     if (appData.oldSaveStyle)
10143       return SaveGameOldStyle(f);
10144     else
10145       return SaveGamePGN(f);
10146 }
10147
10148 /* Save the current position to the given file */
10149 int
10150 SavePositionToFile(filename)
10151      char *filename;
10152 {
10153     FILE *f;
10154     char buf[MSG_SIZ];
10155
10156     if (strcmp(filename, "-") == 0) {
10157         return SavePosition(stdout, 0, NULL);
10158     } else {
10159         f = fopen(filename, "a");
10160         if (f == NULL) {
10161             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10162             DisplayError(buf, errno);
10163             return FALSE;
10164         } else {
10165             SavePosition(f, 0, NULL);
10166             return TRUE;
10167         }
10168     }
10169 }
10170
10171 /* Save the current position to the given open file and close the file */
10172 int
10173 SavePosition(f, dummy, dummy2)
10174      FILE *f;
10175      int dummy;
10176      char *dummy2;
10177 {
10178     time_t tm;
10179     char *fen;
10180
10181     if (gameMode == EditPosition) EditPositionDone(TRUE);
10182     if (appData.oldSaveStyle) {
10183         tm = time((time_t *) NULL);
10184     
10185         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10186         PrintOpponents(f);
10187         fprintf(f, "[--------------\n");
10188         PrintPosition(f, currentMove);
10189         fprintf(f, "--------------]\n");
10190     } else {
10191         fen = PositionToFEN(currentMove, NULL);
10192         fprintf(f, "%s\n", fen);
10193         free(fen);
10194     }
10195     fclose(f);
10196     return TRUE;
10197 }
10198
10199 void
10200 ReloadCmailMsgEvent(unregister)
10201      int unregister;
10202 {
10203 #if !WIN32
10204     static char *inFilename = NULL;
10205     static char *outFilename;
10206     int i;
10207     struct stat inbuf, outbuf;
10208     int status;
10209     
10210     /* Any registered moves are unregistered if unregister is set, */
10211     /* i.e. invoked by the signal handler */
10212     if (unregister) {
10213         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10214             cmailMoveRegistered[i] = FALSE;
10215             if (cmailCommentList[i] != NULL) {
10216                 free(cmailCommentList[i]);
10217                 cmailCommentList[i] = NULL;
10218             }
10219         }
10220         nCmailMovesRegistered = 0;
10221     }
10222
10223     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10224         cmailResult[i] = CMAIL_NOT_RESULT;
10225     }
10226     nCmailResults = 0;
10227
10228     if (inFilename == NULL) {
10229         /* Because the filenames are static they only get malloced once  */
10230         /* and they never get freed                                      */
10231         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10232         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10233
10234         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10235         sprintf(outFilename, "%s.out", appData.cmailGameName);
10236     }
10237     
10238     status = stat(outFilename, &outbuf);
10239     if (status < 0) {
10240         cmailMailedMove = FALSE;
10241     } else {
10242         status = stat(inFilename, &inbuf);
10243         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10244     }
10245     
10246     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10247        counts the games, notes how each one terminated, etc.
10248        
10249        It would be nice to remove this kludge and instead gather all
10250        the information while building the game list.  (And to keep it
10251        in the game list nodes instead of having a bunch of fixed-size
10252        parallel arrays.)  Note this will require getting each game's
10253        termination from the PGN tags, as the game list builder does
10254        not process the game moves.  --mann
10255        */
10256     cmailMsgLoaded = TRUE;
10257     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10258     
10259     /* Load first game in the file or popup game menu */
10260     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10261
10262 #endif /* !WIN32 */
10263     return;
10264 }
10265
10266 int
10267 RegisterMove()
10268 {
10269     FILE *f;
10270     char string[MSG_SIZ];
10271
10272     if (   cmailMailedMove
10273         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10274         return TRUE;            /* Allow free viewing  */
10275     }
10276
10277     /* Unregister move to ensure that we don't leave RegisterMove        */
10278     /* with the move registered when the conditions for registering no   */
10279     /* longer hold                                                       */
10280     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10281         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10282         nCmailMovesRegistered --;
10283
10284         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10285           {
10286               free(cmailCommentList[lastLoadGameNumber - 1]);
10287               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10288           }
10289     }
10290
10291     if (cmailOldMove == -1) {
10292         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10293         return FALSE;
10294     }
10295
10296     if (currentMove > cmailOldMove + 1) {
10297         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10298         return FALSE;
10299     }
10300
10301     if (currentMove < cmailOldMove) {
10302         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10303         return FALSE;
10304     }
10305
10306     if (forwardMostMove > currentMove) {
10307         /* Silently truncate extra moves */
10308         TruncateGame();
10309     }
10310
10311     if (   (currentMove == cmailOldMove + 1)
10312         || (   (currentMove == cmailOldMove)
10313             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10314                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10315         if (gameInfo.result != GameUnfinished) {
10316             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10317         }
10318
10319         if (commentList[currentMove] != NULL) {
10320             cmailCommentList[lastLoadGameNumber - 1]
10321               = StrSave(commentList[currentMove]);
10322         }
10323         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10324
10325         if (appData.debugMode)
10326           fprintf(debugFP, "Saving %s for game %d\n",
10327                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10328
10329         sprintf(string,
10330                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10331         
10332         f = fopen(string, "w");
10333         if (appData.oldSaveStyle) {
10334             SaveGameOldStyle(f); /* also closes the file */
10335             
10336             sprintf(string, "%s.pos.out", appData.cmailGameName);
10337             f = fopen(string, "w");
10338             SavePosition(f, 0, NULL); /* also closes the file */
10339         } else {
10340             fprintf(f, "{--------------\n");
10341             PrintPosition(f, currentMove);
10342             fprintf(f, "--------------}\n\n");
10343             
10344             SaveGame(f, 0, NULL); /* also closes the file*/
10345         }
10346         
10347         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10348         nCmailMovesRegistered ++;
10349     } else if (nCmailGames == 1) {
10350         DisplayError(_("You have not made a move yet"), 0);
10351         return FALSE;
10352     }
10353
10354     return TRUE;
10355 }
10356
10357 void
10358 MailMoveEvent()
10359 {
10360 #if !WIN32
10361     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10362     FILE *commandOutput;
10363     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10364     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10365     int nBuffers;
10366     int i;
10367     int archived;
10368     char *arcDir;
10369
10370     if (! cmailMsgLoaded) {
10371         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10372         return;
10373     }
10374
10375     if (nCmailGames == nCmailResults) {
10376         DisplayError(_("No unfinished games"), 0);
10377         return;
10378     }
10379
10380 #if CMAIL_PROHIBIT_REMAIL
10381     if (cmailMailedMove) {
10382         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);
10383         DisplayError(msg, 0);
10384         return;
10385     }
10386 #endif
10387
10388     if (! (cmailMailedMove || RegisterMove())) return;
10389     
10390     if (   cmailMailedMove
10391         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10392         sprintf(string, partCommandString,
10393                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10394         commandOutput = popen(string, "r");
10395
10396         if (commandOutput == NULL) {
10397             DisplayError(_("Failed to invoke cmail"), 0);
10398         } else {
10399             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10400                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10401             }
10402             if (nBuffers > 1) {
10403                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10404                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10405                 nBytes = MSG_SIZ - 1;
10406             } else {
10407                 (void) memcpy(msg, buffer, nBytes);
10408             }
10409             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10410
10411             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10412                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10413
10414                 archived = TRUE;
10415                 for (i = 0; i < nCmailGames; i ++) {
10416                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10417                         archived = FALSE;
10418                     }
10419                 }
10420                 if (   archived
10421                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10422                         != NULL)) {
10423                     sprintf(buffer, "%s/%s.%s.archive",
10424                             arcDir,
10425                             appData.cmailGameName,
10426                             gameInfo.date);
10427                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10428                     cmailMsgLoaded = FALSE;
10429                 }
10430             }
10431
10432             DisplayInformation(msg);
10433             pclose(commandOutput);
10434         }
10435     } else {
10436         if ((*cmailMsg) != '\0') {
10437             DisplayInformation(cmailMsg);
10438         }
10439     }
10440
10441     return;
10442 #endif /* !WIN32 */
10443 }
10444
10445 char *
10446 CmailMsg()
10447 {
10448 #if WIN32
10449     return NULL;
10450 #else
10451     int  prependComma = 0;
10452     char number[5];
10453     char string[MSG_SIZ];       /* Space for game-list */
10454     int  i;
10455     
10456     if (!cmailMsgLoaded) return "";
10457
10458     if (cmailMailedMove) {
10459         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10460     } else {
10461         /* Create a list of games left */
10462         sprintf(string, "[");
10463         for (i = 0; i < nCmailGames; i ++) {
10464             if (! (   cmailMoveRegistered[i]
10465                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10466                 if (prependComma) {
10467                     sprintf(number, ",%d", i + 1);
10468                 } else {
10469                     sprintf(number, "%d", i + 1);
10470                     prependComma = 1;
10471                 }
10472                 
10473                 strcat(string, number);
10474             }
10475         }
10476         strcat(string, "]");
10477
10478         if (nCmailMovesRegistered + nCmailResults == 0) {
10479             switch (nCmailGames) {
10480               case 1:
10481                 sprintf(cmailMsg,
10482                         _("Still need to make move for game\n"));
10483                 break;
10484                 
10485               case 2:
10486                 sprintf(cmailMsg,
10487                         _("Still need to make moves for both games\n"));
10488                 break;
10489                 
10490               default:
10491                 sprintf(cmailMsg,
10492                         _("Still need to make moves for all %d games\n"),
10493                         nCmailGames);
10494                 break;
10495             }
10496         } else {
10497             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10498               case 1:
10499                 sprintf(cmailMsg,
10500                         _("Still need to make a move for game %s\n"),
10501                         string);
10502                 break;
10503                 
10504               case 0:
10505                 if (nCmailResults == nCmailGames) {
10506                     sprintf(cmailMsg, _("No unfinished games\n"));
10507                 } else {
10508                     sprintf(cmailMsg, _("Ready to send mail\n"));
10509                 }
10510                 break;
10511                 
10512               default:
10513                 sprintf(cmailMsg,
10514                         _("Still need to make moves for games %s\n"),
10515                         string);
10516             }
10517         }
10518     }
10519     return cmailMsg;
10520 #endif /* WIN32 */
10521 }
10522
10523 void
10524 ResetGameEvent()
10525 {
10526     if (gameMode == Training)
10527       SetTrainingModeOff();
10528
10529     Reset(TRUE, TRUE);
10530     cmailMsgLoaded = FALSE;
10531     if (appData.icsActive) {
10532       SendToICS(ics_prefix);
10533       SendToICS("refresh\n");
10534     }
10535 }
10536
10537 void
10538 ExitEvent(status)
10539      int status;
10540 {
10541     exiting++;
10542     if (exiting > 2) {
10543       /* Give up on clean exit */
10544       exit(status);
10545     }
10546     if (exiting > 1) {
10547       /* Keep trying for clean exit */
10548       return;
10549     }
10550
10551     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10552
10553     if (telnetISR != NULL) {
10554       RemoveInputSource(telnetISR);
10555     }
10556     if (icsPR != NoProc) {
10557       DestroyChildProcess(icsPR, TRUE);
10558     }
10559
10560     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10561     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10562
10563     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10564     /* make sure this other one finishes before killing it!                  */
10565     if(endingGame) { int count = 0;
10566         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10567         while(endingGame && count++ < 10) DoSleep(1);
10568         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10569     }
10570
10571     /* Kill off chess programs */
10572     if (first.pr != NoProc) {
10573         ExitAnalyzeMode();
10574         
10575         DoSleep( appData.delayBeforeQuit );
10576         SendToProgram("quit\n", &first);
10577         DoSleep( appData.delayAfterQuit );
10578         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10579     }
10580     if (second.pr != NoProc) {
10581         DoSleep( appData.delayBeforeQuit );
10582         SendToProgram("quit\n", &second);
10583         DoSleep( appData.delayAfterQuit );
10584         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10585     }
10586     if (first.isr != NULL) {
10587         RemoveInputSource(first.isr);
10588     }
10589     if (second.isr != NULL) {
10590         RemoveInputSource(second.isr);
10591     }
10592
10593     ShutDownFrontEnd();
10594     exit(status);
10595 }
10596
10597 void
10598 PauseEvent()
10599 {
10600     if (appData.debugMode)
10601         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10602     if (pausing) {
10603         pausing = FALSE;
10604         ModeHighlight();
10605         if (gameMode == MachinePlaysWhite ||
10606             gameMode == MachinePlaysBlack) {
10607             StartClocks();
10608         } else {
10609             DisplayBothClocks();
10610         }
10611         if (gameMode == PlayFromGameFile) {
10612             if (appData.timeDelay >= 0) 
10613                 AutoPlayGameLoop();
10614         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10615             Reset(FALSE, TRUE);
10616             SendToICS(ics_prefix);
10617             SendToICS("refresh\n");
10618         } else if (currentMove < forwardMostMove) {
10619             ForwardInner(forwardMostMove);
10620         }
10621         pauseExamInvalid = FALSE;
10622     } else {
10623         switch (gameMode) {
10624           default:
10625             return;
10626           case IcsExamining:
10627             pauseExamForwardMostMove = forwardMostMove;
10628             pauseExamInvalid = FALSE;
10629             /* fall through */
10630           case IcsObserving:
10631           case IcsPlayingWhite:
10632           case IcsPlayingBlack:
10633             pausing = TRUE;
10634             ModeHighlight();
10635             return;
10636           case PlayFromGameFile:
10637             (void) StopLoadGameTimer();
10638             pausing = TRUE;
10639             ModeHighlight();
10640             break;
10641           case BeginningOfGame:
10642             if (appData.icsActive) return;
10643             /* else fall through */
10644           case MachinePlaysWhite:
10645           case MachinePlaysBlack:
10646           case TwoMachinesPlay:
10647             if (forwardMostMove == 0)
10648               return;           /* don't pause if no one has moved */
10649             if ((gameMode == MachinePlaysWhite &&
10650                  !WhiteOnMove(forwardMostMove)) ||
10651                 (gameMode == MachinePlaysBlack &&
10652                  WhiteOnMove(forwardMostMove))) {
10653                 StopClocks();
10654             }
10655             pausing = TRUE;
10656             ModeHighlight();
10657             break;
10658         }
10659     }
10660 }
10661
10662 void
10663 EditCommentEvent()
10664 {
10665     char title[MSG_SIZ];
10666
10667     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10668         strcpy(title, _("Edit comment"));
10669     } else {
10670         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10671                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10672                 parseList[currentMove - 1]);
10673     }
10674
10675     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10676 }
10677
10678
10679 void
10680 EditTagsEvent()
10681 {
10682     char *tags = PGNTags(&gameInfo);
10683     EditTagsPopUp(tags);
10684     free(tags);
10685 }
10686
10687 void
10688 AnalyzeModeEvent()
10689 {
10690     if (appData.noChessProgram || gameMode == AnalyzeMode)
10691       return;
10692
10693     if (gameMode != AnalyzeFile) {
10694         if (!appData.icsEngineAnalyze) {
10695                EditGameEvent();
10696                if (gameMode != EditGame) return;
10697         }
10698         ResurrectChessProgram();
10699         SendToProgram("analyze\n", &first);
10700         first.analyzing = TRUE;
10701         /*first.maybeThinking = TRUE;*/
10702         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10703         EngineOutputPopUp();
10704     }
10705     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10706     pausing = FALSE;
10707     ModeHighlight();
10708     SetGameInfo();
10709
10710     StartAnalysisClock();
10711     GetTimeMark(&lastNodeCountTime);
10712     lastNodeCount = 0;
10713 }
10714
10715 void
10716 AnalyzeFileEvent()
10717 {
10718     if (appData.noChessProgram || gameMode == AnalyzeFile)
10719       return;
10720
10721     if (gameMode != AnalyzeMode) {
10722         EditGameEvent();
10723         if (gameMode != EditGame) return;
10724         ResurrectChessProgram();
10725         SendToProgram("analyze\n", &first);
10726         first.analyzing = TRUE;
10727         /*first.maybeThinking = TRUE;*/
10728         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10729         EngineOutputPopUp();
10730     }
10731     gameMode = AnalyzeFile;
10732     pausing = FALSE;
10733     ModeHighlight();
10734     SetGameInfo();
10735
10736     StartAnalysisClock();
10737     GetTimeMark(&lastNodeCountTime);
10738     lastNodeCount = 0;
10739 }
10740
10741 void
10742 MachineWhiteEvent()
10743 {
10744     char buf[MSG_SIZ];
10745     char *bookHit = NULL;
10746
10747     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10748       return;
10749
10750
10751     if (gameMode == PlayFromGameFile || 
10752         gameMode == TwoMachinesPlay  || 
10753         gameMode == Training         || 
10754         gameMode == AnalyzeMode      || 
10755         gameMode == EndOfGame)
10756         EditGameEvent();
10757
10758     if (gameMode == EditPosition) 
10759         EditPositionDone(TRUE);
10760
10761     if (!WhiteOnMove(currentMove)) {
10762         DisplayError(_("It is not White's turn"), 0);
10763         return;
10764     }
10765   
10766     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10767       ExitAnalyzeMode();
10768
10769     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10770         gameMode == AnalyzeFile)
10771         TruncateGame();
10772
10773     ResurrectChessProgram();    /* in case it isn't running */
10774     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10775         gameMode = MachinePlaysWhite;
10776         ResetClocks();
10777     } else
10778     gameMode = MachinePlaysWhite;
10779     pausing = FALSE;
10780     ModeHighlight();
10781     SetGameInfo();
10782     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10783     DisplayTitle(buf);
10784     if (first.sendName) {
10785       sprintf(buf, "name %s\n", gameInfo.black);
10786       SendToProgram(buf, &first);
10787     }
10788     if (first.sendTime) {
10789       if (first.useColors) {
10790         SendToProgram("black\n", &first); /*gnu kludge*/
10791       }
10792       SendTimeRemaining(&first, TRUE);
10793     }
10794     if (first.useColors) {
10795       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10796     }
10797     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10798     SetMachineThinkingEnables();
10799     first.maybeThinking = TRUE;
10800     StartClocks();
10801     firstMove = FALSE;
10802
10803     if (appData.autoFlipView && !flipView) {
10804       flipView = !flipView;
10805       DrawPosition(FALSE, NULL);
10806       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10807     }
10808
10809     if(bookHit) { // [HGM] book: simulate book reply
10810         static char bookMove[MSG_SIZ]; // a bit generous?
10811
10812         programStats.nodes = programStats.depth = programStats.time = 
10813         programStats.score = programStats.got_only_move = 0;
10814         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10815
10816         strcpy(bookMove, "move ");
10817         strcat(bookMove, bookHit);
10818         HandleMachineMove(bookMove, &first);
10819     }
10820 }
10821
10822 void
10823 MachineBlackEvent()
10824 {
10825     char buf[MSG_SIZ];
10826    char *bookHit = NULL;
10827
10828     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10829         return;
10830
10831
10832     if (gameMode == PlayFromGameFile || 
10833         gameMode == TwoMachinesPlay  || 
10834         gameMode == Training         || 
10835         gameMode == AnalyzeMode      || 
10836         gameMode == EndOfGame)
10837         EditGameEvent();
10838
10839     if (gameMode == EditPosition) 
10840         EditPositionDone(TRUE);
10841
10842     if (WhiteOnMove(currentMove)) {
10843         DisplayError(_("It is not Black's turn"), 0);
10844         return;
10845     }
10846     
10847     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10848       ExitAnalyzeMode();
10849
10850     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10851         gameMode == AnalyzeFile)
10852         TruncateGame();
10853
10854     ResurrectChessProgram();    /* in case it isn't running */
10855     gameMode = MachinePlaysBlack;
10856     pausing = FALSE;
10857     ModeHighlight();
10858     SetGameInfo();
10859     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10860     DisplayTitle(buf);
10861     if (first.sendName) {
10862       sprintf(buf, "name %s\n", gameInfo.white);
10863       SendToProgram(buf, &first);
10864     }
10865     if (first.sendTime) {
10866       if (first.useColors) {
10867         SendToProgram("white\n", &first); /*gnu kludge*/
10868       }
10869       SendTimeRemaining(&first, FALSE);
10870     }
10871     if (first.useColors) {
10872       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10873     }
10874     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10875     SetMachineThinkingEnables();
10876     first.maybeThinking = TRUE;
10877     StartClocks();
10878
10879     if (appData.autoFlipView && flipView) {
10880       flipView = !flipView;
10881       DrawPosition(FALSE, NULL);
10882       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10883     }
10884     if(bookHit) { // [HGM] book: simulate book reply
10885         static char bookMove[MSG_SIZ]; // a bit generous?
10886
10887         programStats.nodes = programStats.depth = programStats.time = 
10888         programStats.score = programStats.got_only_move = 0;
10889         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10890
10891         strcpy(bookMove, "move ");
10892         strcat(bookMove, bookHit);
10893         HandleMachineMove(bookMove, &first);
10894     }
10895 }
10896
10897
10898 void
10899 DisplayTwoMachinesTitle()
10900 {
10901     char buf[MSG_SIZ];
10902     if (appData.matchGames > 0) {
10903         if (first.twoMachinesColor[0] == 'w') {
10904             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10905                     gameInfo.white, gameInfo.black,
10906                     first.matchWins, second.matchWins,
10907                     matchGame - 1 - (first.matchWins + second.matchWins));
10908         } else {
10909             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10910                     gameInfo.white, gameInfo.black,
10911                     second.matchWins, first.matchWins,
10912                     matchGame - 1 - (first.matchWins + second.matchWins));
10913         }
10914     } else {
10915         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10916     }
10917     DisplayTitle(buf);
10918 }
10919
10920 void
10921 TwoMachinesEvent P((void))
10922 {
10923     int i;
10924     char buf[MSG_SIZ];
10925     ChessProgramState *onmove;
10926     char *bookHit = NULL;
10927     
10928     if (appData.noChessProgram) return;
10929
10930     switch (gameMode) {
10931       case TwoMachinesPlay:
10932         return;
10933       case MachinePlaysWhite:
10934       case MachinePlaysBlack:
10935         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10936             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10937             return;
10938         }
10939         /* fall through */
10940       case BeginningOfGame:
10941       case PlayFromGameFile:
10942       case EndOfGame:
10943         EditGameEvent();
10944         if (gameMode != EditGame) return;
10945         break;
10946       case EditPosition:
10947         EditPositionDone(TRUE);
10948         break;
10949       case AnalyzeMode:
10950       case AnalyzeFile:
10951         ExitAnalyzeMode();
10952         break;
10953       case EditGame:
10954       default:
10955         break;
10956     }
10957
10958     forwardMostMove = currentMove;
10959     ResurrectChessProgram();    /* in case first program isn't running */
10960
10961     if (second.pr == NULL) {
10962         StartChessProgram(&second);
10963         if (second.protocolVersion == 1) {
10964           TwoMachinesEventIfReady();
10965         } else {
10966           /* kludge: allow timeout for initial "feature" command */
10967           FreezeUI();
10968           DisplayMessage("", _("Starting second chess program"));
10969           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10970         }
10971         return;
10972     }
10973     DisplayMessage("", "");
10974     InitChessProgram(&second, FALSE);
10975     SendToProgram("force\n", &second);
10976     if (startedFromSetupPosition) {
10977         SendBoard(&second, backwardMostMove);
10978     if (appData.debugMode) {
10979         fprintf(debugFP, "Two Machines\n");
10980     }
10981     }
10982     for (i = backwardMostMove; i < forwardMostMove; i++) {
10983         SendMoveToProgram(i, &second);
10984     }
10985
10986     gameMode = TwoMachinesPlay;
10987     pausing = FALSE;
10988     ModeHighlight();
10989     SetGameInfo();
10990     DisplayTwoMachinesTitle();
10991     firstMove = TRUE;
10992     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10993         onmove = &first;
10994     } else {
10995         onmove = &second;
10996     }
10997
10998     SendToProgram(first.computerString, &first);
10999     if (first.sendName) {
11000       sprintf(buf, "name %s\n", second.tidy);
11001       SendToProgram(buf, &first);
11002     }
11003     SendToProgram(second.computerString, &second);
11004     if (second.sendName) {
11005       sprintf(buf, "name %s\n", first.tidy);
11006       SendToProgram(buf, &second);
11007     }
11008
11009     ResetClocks();
11010     if (!first.sendTime || !second.sendTime) {
11011         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11012         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11013     }
11014     if (onmove->sendTime) {
11015       if (onmove->useColors) {
11016         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11017       }
11018       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11019     }
11020     if (onmove->useColors) {
11021       SendToProgram(onmove->twoMachinesColor, onmove);
11022     }
11023     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11024 //    SendToProgram("go\n", onmove);
11025     onmove->maybeThinking = TRUE;
11026     SetMachineThinkingEnables();
11027
11028     StartClocks();
11029
11030     if(bookHit) { // [HGM] book: simulate book reply
11031         static char bookMove[MSG_SIZ]; // a bit generous?
11032
11033         programStats.nodes = programStats.depth = programStats.time = 
11034         programStats.score = programStats.got_only_move = 0;
11035         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11036
11037         strcpy(bookMove, "move ");
11038         strcat(bookMove, bookHit);
11039         savedMessage = bookMove; // args for deferred call
11040         savedState = onmove;
11041         ScheduleDelayedEvent(DeferredBookMove, 1);
11042     }
11043 }
11044
11045 void
11046 TrainingEvent()
11047 {
11048     if (gameMode == Training) {
11049       SetTrainingModeOff();
11050       gameMode = PlayFromGameFile;
11051       DisplayMessage("", _("Training mode off"));
11052     } else {
11053       gameMode = Training;
11054       animateTraining = appData.animate;
11055
11056       /* make sure we are not already at the end of the game */
11057       if (currentMove < forwardMostMove) {
11058         SetTrainingModeOn();
11059         DisplayMessage("", _("Training mode on"));
11060       } else {
11061         gameMode = PlayFromGameFile;
11062         DisplayError(_("Already at end of game"), 0);
11063       }
11064     }
11065     ModeHighlight();
11066 }
11067
11068 void
11069 IcsClientEvent()
11070 {
11071     if (!appData.icsActive) return;
11072     switch (gameMode) {
11073       case IcsPlayingWhite:
11074       case IcsPlayingBlack:
11075       case IcsObserving:
11076       case IcsIdle:
11077       case BeginningOfGame:
11078       case IcsExamining:
11079         return;
11080
11081       case EditGame:
11082         break;
11083
11084       case EditPosition:
11085         EditPositionDone(TRUE);
11086         break;
11087
11088       case AnalyzeMode:
11089       case AnalyzeFile:
11090         ExitAnalyzeMode();
11091         break;
11092         
11093       default:
11094         EditGameEvent();
11095         break;
11096     }
11097
11098     gameMode = IcsIdle;
11099     ModeHighlight();
11100     return;
11101 }
11102
11103
11104 void
11105 EditGameEvent()
11106 {
11107     int i;
11108
11109     switch (gameMode) {
11110       case Training:
11111         SetTrainingModeOff();
11112         break;
11113       case MachinePlaysWhite:
11114       case MachinePlaysBlack:
11115       case BeginningOfGame:
11116         SendToProgram("force\n", &first);
11117         SetUserThinkingEnables();
11118         break;
11119       case PlayFromGameFile:
11120         (void) StopLoadGameTimer();
11121         if (gameFileFP != NULL) {
11122             gameFileFP = NULL;
11123         }
11124         break;
11125       case EditPosition:
11126         EditPositionDone(TRUE);
11127         break;
11128       case AnalyzeMode:
11129       case AnalyzeFile:
11130         ExitAnalyzeMode();
11131         SendToProgram("force\n", &first);
11132         break;
11133       case TwoMachinesPlay:
11134         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11135         ResurrectChessProgram();
11136         SetUserThinkingEnables();
11137         break;
11138       case EndOfGame:
11139         ResurrectChessProgram();
11140         break;
11141       case IcsPlayingBlack:
11142       case IcsPlayingWhite:
11143         DisplayError(_("Warning: You are still playing a game"), 0);
11144         break;
11145       case IcsObserving:
11146         DisplayError(_("Warning: You are still observing a game"), 0);
11147         break;
11148       case IcsExamining:
11149         DisplayError(_("Warning: You are still examining a game"), 0);
11150         break;
11151       case IcsIdle:
11152         break;
11153       case EditGame:
11154       default:
11155         return;
11156     }
11157     
11158     pausing = FALSE;
11159     StopClocks();
11160     first.offeredDraw = second.offeredDraw = 0;
11161
11162     if (gameMode == PlayFromGameFile) {
11163         whiteTimeRemaining = timeRemaining[0][currentMove];
11164         blackTimeRemaining = timeRemaining[1][currentMove];
11165         DisplayTitle("");
11166     }
11167
11168     if (gameMode == MachinePlaysWhite ||
11169         gameMode == MachinePlaysBlack ||
11170         gameMode == TwoMachinesPlay ||
11171         gameMode == EndOfGame) {
11172         i = forwardMostMove;
11173         while (i > currentMove) {
11174             SendToProgram("undo\n", &first);
11175             i--;
11176         }
11177         whiteTimeRemaining = timeRemaining[0][currentMove];
11178         blackTimeRemaining = timeRemaining[1][currentMove];
11179         DisplayBothClocks();
11180         if (whiteFlag || blackFlag) {
11181             whiteFlag = blackFlag = 0;
11182         }
11183         DisplayTitle("");
11184     }           
11185     
11186     gameMode = EditGame;
11187     ModeHighlight();
11188     SetGameInfo();
11189 }
11190
11191
11192 void
11193 EditPositionEvent()
11194 {
11195     if (gameMode == EditPosition) {
11196         EditGameEvent();
11197         return;
11198     }
11199     
11200     EditGameEvent();
11201     if (gameMode != EditGame) return;
11202     
11203     gameMode = EditPosition;
11204     ModeHighlight();
11205     SetGameInfo();
11206     if (currentMove > 0)
11207       CopyBoard(boards[0], boards[currentMove]);
11208     
11209     blackPlaysFirst = !WhiteOnMove(currentMove);
11210     ResetClocks();
11211     currentMove = forwardMostMove = backwardMostMove = 0;
11212     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11213     DisplayMove(-1);
11214 }
11215
11216 void
11217 ExitAnalyzeMode()
11218 {
11219     /* [DM] icsEngineAnalyze - possible call from other functions */
11220     if (appData.icsEngineAnalyze) {
11221         appData.icsEngineAnalyze = FALSE;
11222
11223         DisplayMessage("",_("Close ICS engine analyze..."));
11224     }
11225     if (first.analysisSupport && first.analyzing) {
11226       SendToProgram("exit\n", &first);
11227       first.analyzing = FALSE;
11228     }
11229     thinkOutput[0] = NULLCHAR;
11230 }
11231
11232 void
11233 EditPositionDone(Boolean fakeRights)
11234 {
11235     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11236
11237     startedFromSetupPosition = TRUE;
11238     InitChessProgram(&first, FALSE);
11239     if(fakeRights)  
11240       { /* don't do this if we just pasted FEN */
11241         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11242         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11243           {
11244             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11245             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11246           } 
11247         else 
11248           castlingRights[0][2] = -1;
11249         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11250           {
11251             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11252             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11253           } 
11254         else 
11255           castlingRights[0][5] = -1;
11256       }
11257     SendToProgram("force\n", &first);
11258     if (blackPlaysFirst) {
11259         strcpy(moveList[0], "");
11260         strcpy(parseList[0], "");
11261         currentMove = forwardMostMove = backwardMostMove = 1;
11262         CopyBoard(boards[1], boards[0]);
11263         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11264         { int i;
11265           epStatus[1] = epStatus[0];
11266           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11267         }
11268     } else {
11269         currentMove = forwardMostMove = backwardMostMove = 0;
11270     }
11271     SendBoard(&first, forwardMostMove);
11272     if (appData.debugMode) {
11273         fprintf(debugFP, "EditPosDone\n");
11274     }
11275     DisplayTitle("");
11276     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11277     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11278     gameMode = EditGame;
11279     ModeHighlight();
11280     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11281     ClearHighlights(); /* [AS] */
11282 }
11283
11284 /* Pause for `ms' milliseconds */
11285 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11286 void
11287 TimeDelay(ms)
11288      long ms;
11289 {
11290     TimeMark m1, m2;
11291
11292     GetTimeMark(&m1);
11293     do {
11294         GetTimeMark(&m2);
11295     } while (SubtractTimeMarks(&m2, &m1) < ms);
11296 }
11297
11298 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11299 void
11300 SendMultiLineToICS(buf)
11301      char *buf;
11302 {
11303     char temp[MSG_SIZ+1], *p;
11304     int len;
11305
11306     len = strlen(buf);
11307     if (len > MSG_SIZ)
11308       len = MSG_SIZ;
11309   
11310     strncpy(temp, buf, len);
11311     temp[len] = 0;
11312
11313     p = temp;
11314     while (*p) {
11315         if (*p == '\n' || *p == '\r')
11316           *p = ' ';
11317         ++p;
11318     }
11319
11320     strcat(temp, "\n");
11321     SendToICS(temp);
11322     SendToPlayer(temp, strlen(temp));
11323 }
11324
11325 void
11326 SetWhiteToPlayEvent()
11327 {
11328     if (gameMode == EditPosition) {
11329         blackPlaysFirst = FALSE;
11330         DisplayBothClocks();    /* works because currentMove is 0 */
11331     } else if (gameMode == IcsExamining) {
11332         SendToICS(ics_prefix);
11333         SendToICS("tomove white\n");
11334     }
11335 }
11336
11337 void
11338 SetBlackToPlayEvent()
11339 {
11340     if (gameMode == EditPosition) {
11341         blackPlaysFirst = TRUE;
11342         currentMove = 1;        /* kludge */
11343         DisplayBothClocks();
11344         currentMove = 0;
11345     } else if (gameMode == IcsExamining) {
11346         SendToICS(ics_prefix);
11347         SendToICS("tomove black\n");
11348     }
11349 }
11350
11351 void
11352 EditPositionMenuEvent(selection, x, y)
11353      ChessSquare selection;
11354      int x, y;
11355 {
11356     char buf[MSG_SIZ];
11357     ChessSquare piece = boards[0][y][x];
11358
11359     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11360
11361     switch (selection) {
11362       case ClearBoard:
11363         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11364             SendToICS(ics_prefix);
11365             SendToICS("bsetup clear\n");
11366         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11367             SendToICS(ics_prefix);
11368             SendToICS("clearboard\n");
11369         } else {
11370             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11371                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11372                 for (y = 0; y < BOARD_HEIGHT; y++) {
11373                     if (gameMode == IcsExamining) {
11374                         if (boards[currentMove][y][x] != EmptySquare) {
11375                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11376                                     AAA + x, ONE + y);
11377                             SendToICS(buf);
11378                         }
11379                     } else {
11380                         boards[0][y][x] = p;
11381                     }
11382                 }
11383             }
11384         }
11385         if (gameMode == EditPosition) {
11386             DrawPosition(FALSE, boards[0]);
11387         }
11388         break;
11389
11390       case WhitePlay:
11391         SetWhiteToPlayEvent();
11392         break;
11393
11394       case BlackPlay:
11395         SetBlackToPlayEvent();
11396         break;
11397
11398       case EmptySquare:
11399         if (gameMode == IcsExamining) {
11400             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11401             SendToICS(buf);
11402         } else {
11403             boards[0][y][x] = EmptySquare;
11404             DrawPosition(FALSE, boards[0]);
11405         }
11406         break;
11407
11408       case PromotePiece:
11409         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11410            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11411             selection = (ChessSquare) (PROMOTED piece);
11412         } else if(piece == EmptySquare) selection = WhiteSilver;
11413         else selection = (ChessSquare)((int)piece - 1);
11414         goto defaultlabel;
11415
11416       case DemotePiece:
11417         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11418            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11419             selection = (ChessSquare) (DEMOTED piece);
11420         } else if(piece == EmptySquare) selection = BlackSilver;
11421         else selection = (ChessSquare)((int)piece + 1);       
11422         goto defaultlabel;
11423
11424       case WhiteQueen:
11425       case BlackQueen:
11426         if(gameInfo.variant == VariantShatranj ||
11427            gameInfo.variant == VariantXiangqi  ||
11428            gameInfo.variant == VariantCourier  ||
11429            gameInfo.variant == VariantMakruk     )
11430             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11431         goto defaultlabel;
11432
11433       case WhiteKing:
11434       case BlackKing:
11435         if(gameInfo.variant == VariantXiangqi)
11436             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11437         if(gameInfo.variant == VariantKnightmate)
11438             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11439       default:
11440         defaultlabel:
11441         if (gameMode == IcsExamining) {
11442             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11443                     PieceToChar(selection), AAA + x, ONE + y);
11444             SendToICS(buf);
11445         } else {
11446             boards[0][y][x] = selection;
11447             DrawPosition(FALSE, boards[0]);
11448         }
11449         break;
11450     }
11451 }
11452
11453
11454 void
11455 DropMenuEvent(selection, x, y)
11456      ChessSquare selection;
11457      int x, y;
11458 {
11459     ChessMove moveType;
11460
11461     switch (gameMode) {
11462       case IcsPlayingWhite:
11463       case MachinePlaysBlack:
11464         if (!WhiteOnMove(currentMove)) {
11465             DisplayMoveError(_("It is Black's turn"));
11466             return;
11467         }
11468         moveType = WhiteDrop;
11469         break;
11470       case IcsPlayingBlack:
11471       case MachinePlaysWhite:
11472         if (WhiteOnMove(currentMove)) {
11473             DisplayMoveError(_("It is White's turn"));
11474             return;
11475         }
11476         moveType = BlackDrop;
11477         break;
11478       case EditGame:
11479         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11480         break;
11481       default:
11482         return;
11483     }
11484
11485     if (moveType == BlackDrop && selection < BlackPawn) {
11486       selection = (ChessSquare) ((int) selection
11487                                  + (int) BlackPawn - (int) WhitePawn);
11488     }
11489     if (boards[currentMove][y][x] != EmptySquare) {
11490         DisplayMoveError(_("That square is occupied"));
11491         return;
11492     }
11493
11494     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11495 }
11496
11497 void
11498 AcceptEvent()
11499 {
11500     /* Accept a pending offer of any kind from opponent */
11501     
11502     if (appData.icsActive) {
11503         SendToICS(ics_prefix);
11504         SendToICS("accept\n");
11505     } else if (cmailMsgLoaded) {
11506         if (currentMove == cmailOldMove &&
11507             commentList[cmailOldMove] != NULL &&
11508             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11509                    "Black offers a draw" : "White offers a draw")) {
11510             TruncateGame();
11511             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11512             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11513         } else {
11514             DisplayError(_("There is no pending offer on this move"), 0);
11515             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11516         }
11517     } else {
11518         /* Not used for offers from chess program */
11519     }
11520 }
11521
11522 void
11523 DeclineEvent()
11524 {
11525     /* Decline a pending offer of any kind from opponent */
11526     
11527     if (appData.icsActive) {
11528         SendToICS(ics_prefix);
11529         SendToICS("decline\n");
11530     } else if (cmailMsgLoaded) {
11531         if (currentMove == cmailOldMove &&
11532             commentList[cmailOldMove] != NULL &&
11533             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11534                    "Black offers a draw" : "White offers a draw")) {
11535 #ifdef NOTDEF
11536             AppendComment(cmailOldMove, "Draw declined");
11537             DisplayComment(cmailOldMove - 1, "Draw declined");
11538 #endif /*NOTDEF*/
11539         } else {
11540             DisplayError(_("There is no pending offer on this move"), 0);
11541         }
11542     } else {
11543         /* Not used for offers from chess program */
11544     }
11545 }
11546
11547 void
11548 RematchEvent()
11549 {
11550     /* Issue ICS rematch command */
11551     if (appData.icsActive) {
11552         SendToICS(ics_prefix);
11553         SendToICS("rematch\n");
11554     }
11555 }
11556
11557 void
11558 CallFlagEvent()
11559 {
11560     /* Call your opponent's flag (claim a win on time) */
11561     if (appData.icsActive) {
11562         SendToICS(ics_prefix);
11563         SendToICS("flag\n");
11564     } else {
11565         switch (gameMode) {
11566           default:
11567             return;
11568           case MachinePlaysWhite:
11569             if (whiteFlag) {
11570                 if (blackFlag)
11571                   GameEnds(GameIsDrawn, "Both players ran out of time",
11572                            GE_PLAYER);
11573                 else
11574                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11575             } else {
11576                 DisplayError(_("Your opponent is not out of time"), 0);
11577             }
11578             break;
11579           case MachinePlaysBlack:
11580             if (blackFlag) {
11581                 if (whiteFlag)
11582                   GameEnds(GameIsDrawn, "Both players ran out of time",
11583                            GE_PLAYER);
11584                 else
11585                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11586             } else {
11587                 DisplayError(_("Your opponent is not out of time"), 0);
11588             }
11589             break;
11590         }
11591     }
11592 }
11593
11594 void
11595 DrawEvent()
11596 {
11597     /* Offer draw or accept pending draw offer from opponent */
11598     
11599     if (appData.icsActive) {
11600         /* Note: tournament rules require draw offers to be
11601            made after you make your move but before you punch
11602            your clock.  Currently ICS doesn't let you do that;
11603            instead, you immediately punch your clock after making
11604            a move, but you can offer a draw at any time. */
11605         
11606         SendToICS(ics_prefix);
11607         SendToICS("draw\n");
11608     } else if (cmailMsgLoaded) {
11609         if (currentMove == cmailOldMove &&
11610             commentList[cmailOldMove] != NULL &&
11611             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11612                    "Black offers a draw" : "White offers a draw")) {
11613             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11614             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11615         } else if (currentMove == cmailOldMove + 1) {
11616             char *offer = WhiteOnMove(cmailOldMove) ?
11617               "White offers a draw" : "Black offers a draw";
11618             AppendComment(currentMove, offer);
11619             DisplayComment(currentMove - 1, offer);
11620             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11621         } else {
11622             DisplayError(_("You must make your move before offering a draw"), 0);
11623             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11624         }
11625     } else if (first.offeredDraw) {
11626         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11627     } else {
11628         if (first.sendDrawOffers) {
11629             SendToProgram("draw\n", &first);
11630             userOfferedDraw = TRUE;
11631         }
11632     }
11633 }
11634
11635 void
11636 AdjournEvent()
11637 {
11638     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11639     
11640     if (appData.icsActive) {
11641         SendToICS(ics_prefix);
11642         SendToICS("adjourn\n");
11643     } else {
11644         /* Currently GNU Chess doesn't offer or accept Adjourns */
11645     }
11646 }
11647
11648
11649 void
11650 AbortEvent()
11651 {
11652     /* Offer Abort or accept pending Abort offer from opponent */
11653     
11654     if (appData.icsActive) {
11655         SendToICS(ics_prefix);
11656         SendToICS("abort\n");
11657     } else {
11658         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11659     }
11660 }
11661
11662 void
11663 ResignEvent()
11664 {
11665     /* Resign.  You can do this even if it's not your turn. */
11666     
11667     if (appData.icsActive) {
11668         SendToICS(ics_prefix);
11669         SendToICS("resign\n");
11670     } else {
11671         switch (gameMode) {
11672           case MachinePlaysWhite:
11673             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11674             break;
11675           case MachinePlaysBlack:
11676             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11677             break;
11678           case EditGame:
11679             if (cmailMsgLoaded) {
11680                 TruncateGame();
11681                 if (WhiteOnMove(cmailOldMove)) {
11682                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11683                 } else {
11684                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11685                 }
11686                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11687             }
11688             break;
11689           default:
11690             break;
11691         }
11692     }
11693 }
11694
11695
11696 void
11697 StopObservingEvent()
11698 {
11699     /* Stop observing current games */
11700     SendToICS(ics_prefix);
11701     SendToICS("unobserve\n");
11702 }
11703
11704 void
11705 StopExaminingEvent()
11706 {
11707     /* Stop observing current game */
11708     SendToICS(ics_prefix);
11709     SendToICS("unexamine\n");
11710 }
11711
11712 void
11713 ForwardInner(target)
11714      int target;
11715 {
11716     int limit;
11717
11718     if (appData.debugMode)
11719         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11720                 target, currentMove, forwardMostMove);
11721
11722     if (gameMode == EditPosition)
11723       return;
11724
11725     if (gameMode == PlayFromGameFile && !pausing)
11726       PauseEvent();
11727     
11728     if (gameMode == IcsExamining && pausing)
11729       limit = pauseExamForwardMostMove;
11730     else
11731       limit = forwardMostMove;
11732     
11733     if (target > limit) target = limit;
11734
11735     if (target > 0 && moveList[target - 1][0]) {
11736         int fromX, fromY, toX, toY;
11737         toX = moveList[target - 1][2] - AAA;
11738         toY = moveList[target - 1][3] - ONE;
11739         if (moveList[target - 1][1] == '@') {
11740             if (appData.highlightLastMove) {
11741                 SetHighlights(-1, -1, toX, toY);
11742             }
11743         } else {
11744             fromX = moveList[target - 1][0] - AAA;
11745             fromY = moveList[target - 1][1] - ONE;
11746             if (target == currentMove + 1) {
11747                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11748             }
11749             if (appData.highlightLastMove) {
11750                 SetHighlights(fromX, fromY, toX, toY);
11751             }
11752         }
11753     }
11754     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11755         gameMode == Training || gameMode == PlayFromGameFile || 
11756         gameMode == AnalyzeFile) {
11757         while (currentMove < target) {
11758             SendMoveToProgram(currentMove++, &first);
11759         }
11760     } else {
11761         currentMove = target;
11762     }
11763     
11764     if (gameMode == EditGame || gameMode == EndOfGame) {
11765         whiteTimeRemaining = timeRemaining[0][currentMove];
11766         blackTimeRemaining = timeRemaining[1][currentMove];
11767     }
11768     DisplayBothClocks();
11769     DisplayMove(currentMove - 1);
11770     DrawPosition(FALSE, boards[currentMove]);
11771     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11772     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11773         DisplayComment(currentMove - 1, commentList[currentMove]);
11774     }
11775 }
11776
11777
11778 void
11779 ForwardEvent()
11780 {
11781     if (gameMode == IcsExamining && !pausing) {
11782         SendToICS(ics_prefix);
11783         SendToICS("forward\n");
11784     } else {
11785         ForwardInner(currentMove + 1);
11786     }
11787 }
11788
11789 void
11790 ToEndEvent()
11791 {
11792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11793         /* to optimze, we temporarily turn off analysis mode while we feed
11794          * the remaining moves to the engine. Otherwise we get analysis output
11795          * after each move.
11796          */ 
11797         if (first.analysisSupport) {
11798           SendToProgram("exit\nforce\n", &first);
11799           first.analyzing = FALSE;
11800         }
11801     }
11802         
11803     if (gameMode == IcsExamining && !pausing) {
11804         SendToICS(ics_prefix);
11805         SendToICS("forward 999999\n");
11806     } else {
11807         ForwardInner(forwardMostMove);
11808     }
11809
11810     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11811         /* we have fed all the moves, so reactivate analysis mode */
11812         SendToProgram("analyze\n", &first);
11813         first.analyzing = TRUE;
11814         /*first.maybeThinking = TRUE;*/
11815         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11816     }
11817 }
11818
11819 void
11820 BackwardInner(target)
11821      int target;
11822 {
11823     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11824
11825     if (appData.debugMode)
11826         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11827                 target, currentMove, forwardMostMove);
11828
11829     if (gameMode == EditPosition) return;
11830     if (currentMove <= backwardMostMove) {
11831         ClearHighlights();
11832         DrawPosition(full_redraw, boards[currentMove]);
11833         return;
11834     }
11835     if (gameMode == PlayFromGameFile && !pausing)
11836       PauseEvent();
11837     
11838     if (moveList[target][0]) {
11839         int fromX, fromY, toX, toY;
11840         toX = moveList[target][2] - AAA;
11841         toY = moveList[target][3] - ONE;
11842         if (moveList[target][1] == '@') {
11843             if (appData.highlightLastMove) {
11844                 SetHighlights(-1, -1, toX, toY);
11845             }
11846         } else {
11847             fromX = moveList[target][0] - AAA;
11848             fromY = moveList[target][1] - ONE;
11849             if (target == currentMove - 1) {
11850                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11851             }
11852             if (appData.highlightLastMove) {
11853                 SetHighlights(fromX, fromY, toX, toY);
11854             }
11855         }
11856     }
11857     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11858         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11859         while (currentMove > target) {
11860             SendToProgram("undo\n", &first);
11861             currentMove--;
11862         }
11863     } else {
11864         currentMove = target;
11865     }
11866     
11867     if (gameMode == EditGame || gameMode == EndOfGame) {
11868         whiteTimeRemaining = timeRemaining[0][currentMove];
11869         blackTimeRemaining = timeRemaining[1][currentMove];
11870     }
11871     DisplayBothClocks();
11872     DisplayMove(currentMove - 1);
11873     DrawPosition(full_redraw, boards[currentMove]);
11874     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11875     // [HGM] PV info: routine tests if comment empty
11876     DisplayComment(currentMove - 1, commentList[currentMove]);
11877 }
11878
11879 void
11880 BackwardEvent()
11881 {
11882     if (gameMode == IcsExamining && !pausing) {
11883         SendToICS(ics_prefix);
11884         SendToICS("backward\n");
11885     } else {
11886         BackwardInner(currentMove - 1);
11887     }
11888 }
11889
11890 void
11891 ToStartEvent()
11892 {
11893     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11894         /* to optimize, we temporarily turn off analysis mode while we undo
11895          * all the moves. Otherwise we get analysis output after each undo.
11896          */ 
11897         if (first.analysisSupport) {
11898           SendToProgram("exit\nforce\n", &first);
11899           first.analyzing = FALSE;
11900         }
11901     }
11902
11903     if (gameMode == IcsExamining && !pausing) {
11904         SendToICS(ics_prefix);
11905         SendToICS("backward 999999\n");
11906     } else {
11907         BackwardInner(backwardMostMove);
11908     }
11909
11910     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11911         /* we have fed all the moves, so reactivate analysis mode */
11912         SendToProgram("analyze\n", &first);
11913         first.analyzing = TRUE;
11914         /*first.maybeThinking = TRUE;*/
11915         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11916     }
11917 }
11918
11919 void
11920 ToNrEvent(int to)
11921 {
11922   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11923   if (to >= forwardMostMove) to = forwardMostMove;
11924   if (to <= backwardMostMove) to = backwardMostMove;
11925   if (to < currentMove) {
11926     BackwardInner(to);
11927   } else {
11928     ForwardInner(to);
11929   }
11930 }
11931
11932 void
11933 RevertEvent()
11934 {
11935     if (gameMode != IcsExamining) {
11936         DisplayError(_("You are not examining a game"), 0);
11937         return;
11938     }
11939     if (pausing) {
11940         DisplayError(_("You can't revert while pausing"), 0);
11941         return;
11942     }
11943     SendToICS(ics_prefix);
11944     SendToICS("revert\n");
11945 }
11946
11947 void
11948 RetractMoveEvent()
11949 {
11950     switch (gameMode) {
11951       case MachinePlaysWhite:
11952       case MachinePlaysBlack:
11953         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11954             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11955             return;
11956         }
11957         if (forwardMostMove < 2) return;
11958         currentMove = forwardMostMove = forwardMostMove - 2;
11959         whiteTimeRemaining = timeRemaining[0][currentMove];
11960         blackTimeRemaining = timeRemaining[1][currentMove];
11961         DisplayBothClocks();
11962         DisplayMove(currentMove - 1);
11963         ClearHighlights();/*!! could figure this out*/
11964         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11965         SendToProgram("remove\n", &first);
11966         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11967         break;
11968
11969       case BeginningOfGame:
11970       default:
11971         break;
11972
11973       case IcsPlayingWhite:
11974       case IcsPlayingBlack:
11975         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11976             SendToICS(ics_prefix);
11977             SendToICS("takeback 2\n");
11978         } else {
11979             SendToICS(ics_prefix);
11980             SendToICS("takeback 1\n");
11981         }
11982         break;
11983     }
11984 }
11985
11986 void
11987 MoveNowEvent()
11988 {
11989     ChessProgramState *cps;
11990
11991     switch (gameMode) {
11992       case MachinePlaysWhite:
11993         if (!WhiteOnMove(forwardMostMove)) {
11994             DisplayError(_("It is your turn"), 0);
11995             return;
11996         }
11997         cps = &first;
11998         break;
11999       case MachinePlaysBlack:
12000         if (WhiteOnMove(forwardMostMove)) {
12001             DisplayError(_("It is your turn"), 0);
12002             return;
12003         }
12004         cps = &first;
12005         break;
12006       case TwoMachinesPlay:
12007         if (WhiteOnMove(forwardMostMove) ==
12008             (first.twoMachinesColor[0] == 'w')) {
12009             cps = &first;
12010         } else {
12011             cps = &second;
12012         }
12013         break;
12014       case BeginningOfGame:
12015       default:
12016         return;
12017     }
12018     SendToProgram("?\n", cps);
12019 }
12020
12021 void
12022 TruncateGameEvent()
12023 {
12024     EditGameEvent();
12025     if (gameMode != EditGame) return;
12026     TruncateGame();
12027 }
12028
12029 void
12030 TruncateGame()
12031 {
12032     if (forwardMostMove > currentMove) {
12033         if (gameInfo.resultDetails != NULL) {
12034             free(gameInfo.resultDetails);
12035             gameInfo.resultDetails = NULL;
12036             gameInfo.result = GameUnfinished;
12037         }
12038         forwardMostMove = currentMove;
12039         HistorySet(parseList, backwardMostMove, forwardMostMove,
12040                    currentMove-1);
12041     }
12042 }
12043
12044 void
12045 HintEvent()
12046 {
12047     if (appData.noChessProgram) return;
12048     switch (gameMode) {
12049       case MachinePlaysWhite:
12050         if (WhiteOnMove(forwardMostMove)) {
12051             DisplayError(_("Wait until your turn"), 0);
12052             return;
12053         }
12054         break;
12055       case BeginningOfGame:
12056       case MachinePlaysBlack:
12057         if (!WhiteOnMove(forwardMostMove)) {
12058             DisplayError(_("Wait until your turn"), 0);
12059             return;
12060         }
12061         break;
12062       default:
12063         DisplayError(_("No hint available"), 0);
12064         return;
12065     }
12066     SendToProgram("hint\n", &first);
12067     hintRequested = TRUE;
12068 }
12069
12070 void
12071 BookEvent()
12072 {
12073     if (appData.noChessProgram) return;
12074     switch (gameMode) {
12075       case MachinePlaysWhite:
12076         if (WhiteOnMove(forwardMostMove)) {
12077             DisplayError(_("Wait until your turn"), 0);
12078             return;
12079         }
12080         break;
12081       case BeginningOfGame:
12082       case MachinePlaysBlack:
12083         if (!WhiteOnMove(forwardMostMove)) {
12084             DisplayError(_("Wait until your turn"), 0);
12085             return;
12086         }
12087         break;
12088       case EditPosition:
12089         EditPositionDone(TRUE);
12090         break;
12091       case TwoMachinesPlay:
12092         return;
12093       default:
12094         break;
12095     }
12096     SendToProgram("bk\n", &first);
12097     bookOutput[0] = NULLCHAR;
12098     bookRequested = TRUE;
12099 }
12100
12101 void
12102 AboutGameEvent()
12103 {
12104     char *tags = PGNTags(&gameInfo);
12105     TagsPopUp(tags, CmailMsg());
12106     free(tags);
12107 }
12108
12109 /* end button procedures */
12110
12111 void
12112 PrintPosition(fp, move)
12113      FILE *fp;
12114      int move;
12115 {
12116     int i, j;
12117     
12118     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12119         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12120             char c = PieceToChar(boards[move][i][j]);
12121             fputc(c == 'x' ? '.' : c, fp);
12122             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12123         }
12124     }
12125     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12126       fprintf(fp, "white to play\n");
12127     else
12128       fprintf(fp, "black to play\n");
12129 }
12130
12131 void
12132 PrintOpponents(fp)
12133      FILE *fp;
12134 {
12135     if (gameInfo.white != NULL) {
12136         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12137     } else {
12138         fprintf(fp, "\n");
12139     }
12140 }
12141
12142 /* Find last component of program's own name, using some heuristics */
12143 void
12144 TidyProgramName(prog, host, buf)
12145      char *prog, *host, buf[MSG_SIZ];
12146 {
12147     char *p, *q;
12148     int local = (strcmp(host, "localhost") == 0);
12149     while (!local && (p = strchr(prog, ';')) != NULL) {
12150         p++;
12151         while (*p == ' ') p++;
12152         prog = p;
12153     }
12154     if (*prog == '"' || *prog == '\'') {
12155         q = strchr(prog + 1, *prog);
12156     } else {
12157         q = strchr(prog, ' ');
12158     }
12159     if (q == NULL) q = prog + strlen(prog);
12160     p = q;
12161     while (p >= prog && *p != '/' && *p != '\\') p--;
12162     p++;
12163     if(p == prog && *p == '"') p++;
12164     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12165     memcpy(buf, p, q - p);
12166     buf[q - p] = NULLCHAR;
12167     if (!local) {
12168         strcat(buf, "@");
12169         strcat(buf, host);
12170     }
12171 }
12172
12173 char *
12174 TimeControlTagValue()
12175 {
12176     char buf[MSG_SIZ];
12177     if (!appData.clockMode) {
12178         strcpy(buf, "-");
12179     } else if (movesPerSession > 0) {
12180         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12181     } else if (timeIncrement == 0) {
12182         sprintf(buf, "%ld", timeControl/1000);
12183     } else {
12184         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12185     }
12186     return StrSave(buf);
12187 }
12188
12189 void
12190 SetGameInfo()
12191 {
12192     /* This routine is used only for certain modes */
12193     VariantClass v = gameInfo.variant;
12194     ClearGameInfo(&gameInfo);
12195     gameInfo.variant = v;
12196
12197     switch (gameMode) {
12198       case MachinePlaysWhite:
12199         gameInfo.event = StrSave( appData.pgnEventHeader );
12200         gameInfo.site = StrSave(HostName());
12201         gameInfo.date = PGNDate();
12202         gameInfo.round = StrSave("-");
12203         gameInfo.white = StrSave(first.tidy);
12204         gameInfo.black = StrSave(UserName());
12205         gameInfo.timeControl = TimeControlTagValue();
12206         break;
12207
12208       case MachinePlaysBlack:
12209         gameInfo.event = StrSave( appData.pgnEventHeader );
12210         gameInfo.site = StrSave(HostName());
12211         gameInfo.date = PGNDate();
12212         gameInfo.round = StrSave("-");
12213         gameInfo.white = StrSave(UserName());
12214         gameInfo.black = StrSave(first.tidy);
12215         gameInfo.timeControl = TimeControlTagValue();
12216         break;
12217
12218       case TwoMachinesPlay:
12219         gameInfo.event = StrSave( appData.pgnEventHeader );
12220         gameInfo.site = StrSave(HostName());
12221         gameInfo.date = PGNDate();
12222         if (matchGame > 0) {
12223             char buf[MSG_SIZ];
12224             sprintf(buf, "%d", matchGame);
12225             gameInfo.round = StrSave(buf);
12226         } else {
12227             gameInfo.round = StrSave("-");
12228         }
12229         if (first.twoMachinesColor[0] == 'w') {
12230             gameInfo.white = StrSave(first.tidy);
12231             gameInfo.black = StrSave(second.tidy);
12232         } else {
12233             gameInfo.white = StrSave(second.tidy);
12234             gameInfo.black = StrSave(first.tidy);
12235         }
12236         gameInfo.timeControl = TimeControlTagValue();
12237         break;
12238
12239       case EditGame:
12240         gameInfo.event = StrSave("Edited game");
12241         gameInfo.site = StrSave(HostName());
12242         gameInfo.date = PGNDate();
12243         gameInfo.round = StrSave("-");
12244         gameInfo.white = StrSave("-");
12245         gameInfo.black = StrSave("-");
12246         break;
12247
12248       case EditPosition:
12249         gameInfo.event = StrSave("Edited position");
12250         gameInfo.site = StrSave(HostName());
12251         gameInfo.date = PGNDate();
12252         gameInfo.round = StrSave("-");
12253         gameInfo.white = StrSave("-");
12254         gameInfo.black = StrSave("-");
12255         break;
12256
12257       case IcsPlayingWhite:
12258       case IcsPlayingBlack:
12259       case IcsObserving:
12260       case IcsExamining:
12261         break;
12262
12263       case PlayFromGameFile:
12264         gameInfo.event = StrSave("Game from non-PGN file");
12265         gameInfo.site = StrSave(HostName());
12266         gameInfo.date = PGNDate();
12267         gameInfo.round = StrSave("-");
12268         gameInfo.white = StrSave("?");
12269         gameInfo.black = StrSave("?");
12270         break;
12271
12272       default:
12273         break;
12274     }
12275 }
12276
12277 void
12278 ReplaceComment(index, text)
12279      int index;
12280      char *text;
12281 {
12282     int len;
12283
12284     while (*text == '\n') text++;
12285     len = strlen(text);
12286     while (len > 0 && text[len - 1] == '\n') len--;
12287
12288     if (commentList[index] != NULL)
12289       free(commentList[index]);
12290
12291     if (len == 0) {
12292         commentList[index] = NULL;
12293         return;
12294     }
12295     commentList[index] = (char *) malloc(len + 2);
12296     strncpy(commentList[index], text, len);
12297     commentList[index][len] = '\n';
12298     commentList[index][len + 1] = NULLCHAR;
12299 }
12300
12301 void
12302 CrushCRs(text)
12303      char *text;
12304 {
12305   char *p = text;
12306   char *q = text;
12307   char ch;
12308
12309   do {
12310     ch = *p++;
12311     if (ch == '\r') continue;
12312     *q++ = ch;
12313   } while (ch != '\0');
12314 }
12315
12316 void
12317 AppendComment(index, text)
12318      int index;
12319      char *text;
12320 {
12321     int oldlen, len;
12322     char *old;
12323
12324     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12325
12326     CrushCRs(text);
12327     while (*text == '\n') text++;
12328     len = strlen(text);
12329     while (len > 0 && text[len - 1] == '\n') len--;
12330
12331     if (len == 0) return;
12332
12333     if (commentList[index] != NULL) {
12334         old = commentList[index];
12335         oldlen = strlen(old);
12336         commentList[index] = (char *) malloc(oldlen + len + 2);
12337         strcpy(commentList[index], old);
12338         free(old);
12339         strncpy(&commentList[index][oldlen], text, len);
12340         commentList[index][oldlen + len] = '\n';
12341         commentList[index][oldlen + len + 1] = NULLCHAR;
12342     } else {
12343         commentList[index] = (char *) malloc(len + 2);
12344         strncpy(commentList[index], text, len);
12345         commentList[index][len] = '\n';
12346         commentList[index][len + 1] = NULLCHAR;
12347     }
12348 }
12349
12350 static char * FindStr( char * text, char * sub_text )
12351 {
12352     char * result = strstr( text, sub_text );
12353
12354     if( result != NULL ) {
12355         result += strlen( sub_text );
12356     }
12357
12358     return result;
12359 }
12360
12361 /* [AS] Try to extract PV info from PGN comment */
12362 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12363 char *GetInfoFromComment( int index, char * text )
12364 {
12365     char * sep = text;
12366
12367     if( text != NULL && index > 0 ) {
12368         int score = 0;
12369         int depth = 0;
12370         int time = -1, sec = 0, deci;
12371         char * s_eval = FindStr( text, "[%eval " );
12372         char * s_emt = FindStr( text, "[%emt " );
12373
12374         if( s_eval != NULL || s_emt != NULL ) {
12375             /* New style */
12376             char delim;
12377
12378             if( s_eval != NULL ) {
12379                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12380                     return text;
12381                 }
12382
12383                 if( delim != ']' ) {
12384                     return text;
12385                 }
12386             }
12387
12388             if( s_emt != NULL ) {
12389             }
12390         }
12391         else {
12392             /* We expect something like: [+|-]nnn.nn/dd */
12393             int score_lo = 0;
12394
12395             sep = strchr( text, '/' );
12396             if( sep == NULL || sep < (text+4) ) {
12397                 return text;
12398             }
12399
12400             time = -1; sec = -1; deci = -1;
12401             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12402                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12403                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12404                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12405                 return text;
12406             }
12407
12408             if( score_lo < 0 || score_lo >= 100 ) {
12409                 return text;
12410             }
12411
12412             if(sec >= 0) time = 600*time + 10*sec; else
12413             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12414
12415             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12416
12417             /* [HGM] PV time: now locate end of PV info */
12418             while( *++sep >= '0' && *sep <= '9'); // strip depth
12419             if(time >= 0)
12420             while( *++sep >= '0' && *sep <= '9'); // strip time
12421             if(sec >= 0)
12422             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12423             if(deci >= 0)
12424             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12425             while(*sep == ' ') sep++;
12426         }
12427
12428         if( depth <= 0 ) {
12429             return text;
12430         }
12431
12432         if( time < 0 ) {
12433             time = -1;
12434         }
12435
12436         pvInfoList[index-1].depth = depth;
12437         pvInfoList[index-1].score = score;
12438         pvInfoList[index-1].time  = 10*time; // centi-sec
12439     }
12440     return sep;
12441 }
12442
12443 void
12444 SendToProgram(message, cps)
12445      char *message;
12446      ChessProgramState *cps;
12447 {
12448     int count, outCount, error;
12449     char buf[MSG_SIZ];
12450
12451     if (cps->pr == NULL) return;
12452     Attention(cps);
12453     
12454     if (appData.debugMode) {
12455         TimeMark now;
12456         GetTimeMark(&now);
12457         fprintf(debugFP, "%ld >%-6s: %s", 
12458                 SubtractTimeMarks(&now, &programStartTime),
12459                 cps->which, message);
12460     }
12461     
12462     count = strlen(message);
12463     outCount = OutputToProcess(cps->pr, message, count, &error);
12464     if (outCount < count && !exiting 
12465                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12466         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12467         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12468             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12469                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12470                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12471             } else {
12472                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12473             }
12474             gameInfo.resultDetails = StrSave(buf);
12475         }
12476         DisplayFatalError(buf, error, 1);
12477     }
12478 }
12479
12480 void
12481 ReceiveFromProgram(isr, closure, message, count, error)
12482      InputSourceRef isr;
12483      VOIDSTAR closure;
12484      char *message;
12485      int count;
12486      int error;
12487 {
12488     char *end_str;
12489     char buf[MSG_SIZ];
12490     ChessProgramState *cps = (ChessProgramState *)closure;
12491
12492     if (isr != cps->isr) return; /* Killed intentionally */
12493     if (count <= 0) {
12494         if (count == 0) {
12495             sprintf(buf,
12496                     _("Error: %s chess program (%s) exited unexpectedly"),
12497                     cps->which, cps->program);
12498         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12499                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12500                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12501                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12502                 } else {
12503                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12504                 }
12505                 gameInfo.resultDetails = StrSave(buf);
12506             }
12507             RemoveInputSource(cps->isr);
12508             DisplayFatalError(buf, 0, 1);
12509         } else {
12510             sprintf(buf,
12511                     _("Error reading from %s chess program (%s)"),
12512                     cps->which, cps->program);
12513             RemoveInputSource(cps->isr);
12514
12515             /* [AS] Program is misbehaving badly... kill it */
12516             if( count == -2 ) {
12517                 DestroyChildProcess( cps->pr, 9 );
12518                 cps->pr = NoProc;
12519             }
12520
12521             DisplayFatalError(buf, error, 1);
12522         }
12523         return;
12524     }
12525     
12526     if ((end_str = strchr(message, '\r')) != NULL)
12527       *end_str = NULLCHAR;
12528     if ((end_str = strchr(message, '\n')) != NULL)
12529       *end_str = NULLCHAR;
12530     
12531     if (appData.debugMode) {
12532         TimeMark now; int print = 1;
12533         char *quote = ""; char c; int i;
12534
12535         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12536                 char start = message[0];
12537                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12538                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12539                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12540                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12541                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12542                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12543                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12544                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12545                         { quote = "# "; print = (appData.engineComments == 2); }
12546                 message[0] = start; // restore original message
12547         }
12548         if(print) {
12549                 GetTimeMark(&now);
12550                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12551                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12552                         quote,
12553                         message);
12554         }
12555     }
12556
12557     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12558     if (appData.icsEngineAnalyze) {
12559         if (strstr(message, "whisper") != NULL ||
12560              strstr(message, "kibitz") != NULL || 
12561             strstr(message, "tellics") != NULL) return;
12562     }
12563
12564     HandleMachineMove(message, cps);
12565 }
12566
12567
12568 void
12569 SendTimeControl(cps, mps, tc, inc, sd, st)
12570      ChessProgramState *cps;
12571      int mps, inc, sd, st;
12572      long tc;
12573 {
12574     char buf[MSG_SIZ];
12575     int seconds;
12576
12577     if( timeControl_2 > 0 ) {
12578         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12579             tc = timeControl_2;
12580         }
12581     }
12582     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12583     inc /= cps->timeOdds;
12584     st  /= cps->timeOdds;
12585
12586     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12587
12588     if (st > 0) {
12589       /* Set exact time per move, normally using st command */
12590       if (cps->stKludge) {
12591         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12592         seconds = st % 60;
12593         if (seconds == 0) {
12594           sprintf(buf, "level 1 %d\n", st/60);
12595         } else {
12596           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12597         }
12598       } else {
12599         sprintf(buf, "st %d\n", st);
12600       }
12601     } else {
12602       /* Set conventional or incremental time control, using level command */
12603       if (seconds == 0) {
12604         /* Note old gnuchess bug -- minutes:seconds used to not work.
12605            Fixed in later versions, but still avoid :seconds
12606            when seconds is 0. */
12607         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12608       } else {
12609         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12610                 seconds, inc/1000);
12611       }
12612     }
12613     SendToProgram(buf, cps);
12614
12615     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12616     /* Orthogonally, limit search to given depth */
12617     if (sd > 0) {
12618       if (cps->sdKludge) {
12619         sprintf(buf, "depth\n%d\n", sd);
12620       } else {
12621         sprintf(buf, "sd %d\n", sd);
12622       }
12623       SendToProgram(buf, cps);
12624     }
12625
12626     if(cps->nps > 0) { /* [HGM] nps */
12627         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12628         else {
12629                 sprintf(buf, "nps %d\n", cps->nps);
12630               SendToProgram(buf, cps);
12631         }
12632     }
12633 }
12634
12635 ChessProgramState *WhitePlayer()
12636 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12637 {
12638     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12639        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12640         return &second;
12641     return &first;
12642 }
12643
12644 void
12645 SendTimeRemaining(cps, machineWhite)
12646      ChessProgramState *cps;
12647      int /*boolean*/ machineWhite;
12648 {
12649     char message[MSG_SIZ];
12650     long time, otime;
12651
12652     /* Note: this routine must be called when the clocks are stopped
12653        or when they have *just* been set or switched; otherwise
12654        it will be off by the time since the current tick started.
12655     */
12656     if (machineWhite) {
12657         time = whiteTimeRemaining / 10;
12658         otime = blackTimeRemaining / 10;
12659     } else {
12660         time = blackTimeRemaining / 10;
12661         otime = whiteTimeRemaining / 10;
12662     }
12663     /* [HGM] translate opponent's time by time-odds factor */
12664     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12665     if (appData.debugMode) {
12666         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12667     }
12668
12669     if (time <= 0) time = 1;
12670     if (otime <= 0) otime = 1;
12671     
12672     sprintf(message, "time %ld\n", time);
12673     SendToProgram(message, cps);
12674
12675     sprintf(message, "otim %ld\n", otime);
12676     SendToProgram(message, cps);
12677 }
12678
12679 int
12680 BoolFeature(p, name, loc, cps)
12681      char **p;
12682      char *name;
12683      int *loc;
12684      ChessProgramState *cps;
12685 {
12686   char buf[MSG_SIZ];
12687   int len = strlen(name);
12688   int val;
12689   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12690     (*p) += len + 1;
12691     sscanf(*p, "%d", &val);
12692     *loc = (val != 0);
12693     while (**p && **p != ' ') (*p)++;
12694     sprintf(buf, "accepted %s\n", name);
12695     SendToProgram(buf, cps);
12696     return TRUE;
12697   }
12698   return FALSE;
12699 }
12700
12701 int
12702 IntFeature(p, name, loc, cps)
12703      char **p;
12704      char *name;
12705      int *loc;
12706      ChessProgramState *cps;
12707 {
12708   char buf[MSG_SIZ];
12709   int len = strlen(name);
12710   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12711     (*p) += len + 1;
12712     sscanf(*p, "%d", loc);
12713     while (**p && **p != ' ') (*p)++;
12714     sprintf(buf, "accepted %s\n", name);
12715     SendToProgram(buf, cps);
12716     return TRUE;
12717   }
12718   return FALSE;
12719 }
12720
12721 int
12722 StringFeature(p, name, loc, cps)
12723      char **p;
12724      char *name;
12725      char loc[];
12726      ChessProgramState *cps;
12727 {
12728   char buf[MSG_SIZ];
12729   int len = strlen(name);
12730   if (strncmp((*p), name, len) == 0
12731       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12732     (*p) += len + 2;
12733     sscanf(*p, "%[^\"]", loc);
12734     while (**p && **p != '\"') (*p)++;
12735     if (**p == '\"') (*p)++;
12736     sprintf(buf, "accepted %s\n", name);
12737     SendToProgram(buf, cps);
12738     return TRUE;
12739   }
12740   return FALSE;
12741 }
12742
12743 int 
12744 ParseOption(Option *opt, ChessProgramState *cps)
12745 // [HGM] options: process the string that defines an engine option, and determine
12746 // name, type, default value, and allowed value range
12747 {
12748         char *p, *q, buf[MSG_SIZ];
12749         int n, min = (-1)<<31, max = 1<<31, def;
12750
12751         if(p = strstr(opt->name, " -spin ")) {
12752             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12753             if(max < min) max = min; // enforce consistency
12754             if(def < min) def = min;
12755             if(def > max) def = max;
12756             opt->value = def;
12757             opt->min = min;
12758             opt->max = max;
12759             opt->type = Spin;
12760         } else if((p = strstr(opt->name, " -slider "))) {
12761             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12762             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12763             if(max < min) max = min; // enforce consistency
12764             if(def < min) def = min;
12765             if(def > max) def = max;
12766             opt->value = def;
12767             opt->min = min;
12768             opt->max = max;
12769             opt->type = Spin; // Slider;
12770         } else if((p = strstr(opt->name, " -string "))) {
12771             opt->textValue = p+9;
12772             opt->type = TextBox;
12773         } else if((p = strstr(opt->name, " -file "))) {
12774             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12775             opt->textValue = p+7;
12776             opt->type = TextBox; // FileName;
12777         } else if((p = strstr(opt->name, " -path "))) {
12778             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12779             opt->textValue = p+7;
12780             opt->type = TextBox; // PathName;
12781         } else if(p = strstr(opt->name, " -check ")) {
12782             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12783             opt->value = (def != 0);
12784             opt->type = CheckBox;
12785         } else if(p = strstr(opt->name, " -combo ")) {
12786             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12787             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12788             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12789             opt->value = n = 0;
12790             while(q = StrStr(q, " /// ")) {
12791                 n++; *q = 0;    // count choices, and null-terminate each of them
12792                 q += 5;
12793                 if(*q == '*') { // remember default, which is marked with * prefix
12794                     q++;
12795                     opt->value = n;
12796                 }
12797                 cps->comboList[cps->comboCnt++] = q;
12798             }
12799             cps->comboList[cps->comboCnt++] = NULL;
12800             opt->max = n + 1;
12801             opt->type = ComboBox;
12802         } else if(p = strstr(opt->name, " -button")) {
12803             opt->type = Button;
12804         } else if(p = strstr(opt->name, " -save")) {
12805             opt->type = SaveButton;
12806         } else return FALSE;
12807         *p = 0; // terminate option name
12808         // now look if the command-line options define a setting for this engine option.
12809         if(cps->optionSettings && cps->optionSettings[0])
12810             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12811         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12812                 sprintf(buf, "option %s", p);
12813                 if(p = strstr(buf, ",")) *p = 0;
12814                 strcat(buf, "\n");
12815                 SendToProgram(buf, cps);
12816         }
12817         return TRUE;
12818 }
12819
12820 void
12821 FeatureDone(cps, val)
12822      ChessProgramState* cps;
12823      int val;
12824 {
12825   DelayedEventCallback cb = GetDelayedEvent();
12826   if ((cb == InitBackEnd3 && cps == &first) ||
12827       (cb == TwoMachinesEventIfReady && cps == &second)) {
12828     CancelDelayedEvent();
12829     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12830   }
12831   cps->initDone = val;
12832 }
12833
12834 /* Parse feature command from engine */
12835 void
12836 ParseFeatures(args, cps)
12837      char* args;
12838      ChessProgramState *cps;  
12839 {
12840   char *p = args;
12841   char *q;
12842   int val;
12843   char buf[MSG_SIZ];
12844
12845   for (;;) {
12846     while (*p == ' ') p++;
12847     if (*p == NULLCHAR) return;
12848
12849     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12850     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12851     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12852     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12853     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12854     if (BoolFeature(&p, "reuse", &val, cps)) {
12855       /* Engine can disable reuse, but can't enable it if user said no */
12856       if (!val) cps->reuse = FALSE;
12857       continue;
12858     }
12859     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12860     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12861       if (gameMode == TwoMachinesPlay) {
12862         DisplayTwoMachinesTitle();
12863       } else {
12864         DisplayTitle("");
12865       }
12866       continue;
12867     }
12868     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12869     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12870     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12871     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12872     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12873     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12874     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12875     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12876     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12877     if (IntFeature(&p, "done", &val, cps)) {
12878       FeatureDone(cps, val);
12879       continue;
12880     }
12881     /* Added by Tord: */
12882     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12883     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12884     /* End of additions by Tord */
12885
12886     /* [HGM] added features: */
12887     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12888     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12889     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12890     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12891     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12892     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12893     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12894         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12895             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12896             SendToProgram(buf, cps);
12897             continue;
12898         }
12899         if(cps->nrOptions >= MAX_OPTIONS) {
12900             cps->nrOptions--;
12901             sprintf(buf, "%s engine has too many options\n", cps->which);
12902             DisplayError(buf, 0);
12903         }
12904         continue;
12905     }
12906     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12907     /* End of additions by HGM */
12908
12909     /* unknown feature: complain and skip */
12910     q = p;
12911     while (*q && *q != '=') q++;
12912     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12913     SendToProgram(buf, cps);
12914     p = q;
12915     if (*p == '=') {
12916       p++;
12917       if (*p == '\"') {
12918         p++;
12919         while (*p && *p != '\"') p++;
12920         if (*p == '\"') p++;
12921       } else {
12922         while (*p && *p != ' ') p++;
12923       }
12924     }
12925   }
12926
12927 }
12928
12929 void
12930 PeriodicUpdatesEvent(newState)
12931      int newState;
12932 {
12933     if (newState == appData.periodicUpdates)
12934       return;
12935
12936     appData.periodicUpdates=newState;
12937
12938     /* Display type changes, so update it now */
12939 //    DisplayAnalysis();
12940
12941     /* Get the ball rolling again... */
12942     if (newState) {
12943         AnalysisPeriodicEvent(1);
12944         StartAnalysisClock();
12945     }
12946 }
12947
12948 void
12949 PonderNextMoveEvent(newState)
12950      int newState;
12951 {
12952     if (newState == appData.ponderNextMove) return;
12953     if (gameMode == EditPosition) EditPositionDone(TRUE);
12954     if (newState) {
12955         SendToProgram("hard\n", &first);
12956         if (gameMode == TwoMachinesPlay) {
12957             SendToProgram("hard\n", &second);
12958         }
12959     } else {
12960         SendToProgram("easy\n", &first);
12961         thinkOutput[0] = NULLCHAR;
12962         if (gameMode == TwoMachinesPlay) {
12963             SendToProgram("easy\n", &second);
12964         }
12965     }
12966     appData.ponderNextMove = newState;
12967 }
12968
12969 void
12970 NewSettingEvent(option, command, value)
12971      char *command;
12972      int option, value;
12973 {
12974     char buf[MSG_SIZ];
12975
12976     if (gameMode == EditPosition) EditPositionDone(TRUE);
12977     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12978     SendToProgram(buf, &first);
12979     if (gameMode == TwoMachinesPlay) {
12980         SendToProgram(buf, &second);
12981     }
12982 }
12983
12984 void
12985 ShowThinkingEvent()
12986 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12987 {
12988     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12989     int newState = appData.showThinking
12990         // [HGM] thinking: other features now need thinking output as well
12991         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12992     
12993     if (oldState == newState) return;
12994     oldState = newState;
12995     if (gameMode == EditPosition) EditPositionDone(TRUE);
12996     if (oldState) {
12997         SendToProgram("post\n", &first);
12998         if (gameMode == TwoMachinesPlay) {
12999             SendToProgram("post\n", &second);
13000         }
13001     } else {
13002         SendToProgram("nopost\n", &first);
13003         thinkOutput[0] = NULLCHAR;
13004         if (gameMode == TwoMachinesPlay) {
13005             SendToProgram("nopost\n", &second);
13006         }
13007     }
13008 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13009 }
13010
13011 void
13012 AskQuestionEvent(title, question, replyPrefix, which)
13013      char *title; char *question; char *replyPrefix; char *which;
13014 {
13015   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13016   if (pr == NoProc) return;
13017   AskQuestion(title, question, replyPrefix, pr);
13018 }
13019
13020 void
13021 DisplayMove(moveNumber)
13022      int moveNumber;
13023 {
13024     char message[MSG_SIZ];
13025     char res[MSG_SIZ];
13026     char cpThinkOutput[MSG_SIZ];
13027
13028     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13029     
13030     if (moveNumber == forwardMostMove - 1 || 
13031         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13032
13033         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13034
13035         if (strchr(cpThinkOutput, '\n')) {
13036             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13037         }
13038     } else {
13039         *cpThinkOutput = NULLCHAR;
13040     }
13041
13042     /* [AS] Hide thinking from human user */
13043     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13044         *cpThinkOutput = NULLCHAR;
13045         if( thinkOutput[0] != NULLCHAR ) {
13046             int i;
13047
13048             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13049                 cpThinkOutput[i] = '.';
13050             }
13051             cpThinkOutput[i] = NULLCHAR;
13052             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13053         }
13054     }
13055
13056     if (moveNumber == forwardMostMove - 1 &&
13057         gameInfo.resultDetails != NULL) {
13058         if (gameInfo.resultDetails[0] == NULLCHAR) {
13059             sprintf(res, " %s", PGNResult(gameInfo.result));
13060         } else {
13061             sprintf(res, " {%s} %s",
13062                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13063         }
13064     } else {
13065         res[0] = NULLCHAR;
13066     }
13067
13068     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13069         DisplayMessage(res, cpThinkOutput);
13070     } else {
13071         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13072                 WhiteOnMove(moveNumber) ? " " : ".. ",
13073                 parseList[moveNumber], res);
13074         DisplayMessage(message, cpThinkOutput);
13075     }
13076 }
13077
13078 void
13079 DisplayComment(moveNumber, text)
13080      int moveNumber;
13081      char *text;
13082 {
13083     char title[MSG_SIZ];
13084     char buf[8000]; // comment can be long!
13085     int score, depth;
13086     
13087     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13088       strcpy(title, "Comment");
13089     } else {
13090       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13091               WhiteOnMove(moveNumber) ? " " : ".. ",
13092               parseList[moveNumber]);
13093     }
13094     // [HGM] PV info: display PV info together with (or as) comment
13095     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13096       if(text == NULL) text = "";                                           
13097       score = pvInfoList[moveNumber].score;
13098       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13099               depth, (pvInfoList[moveNumber].time+50)/100, text);
13100       text = buf;
13101     }
13102     if (text != NULL && (appData.autoDisplayComment || commentUp))
13103         CommentPopUp(title, text);
13104 }
13105
13106 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13107  * might be busy thinking or pondering.  It can be omitted if your
13108  * gnuchess is configured to stop thinking immediately on any user
13109  * input.  However, that gnuchess feature depends on the FIONREAD
13110  * ioctl, which does not work properly on some flavors of Unix.
13111  */
13112 void
13113 Attention(cps)
13114      ChessProgramState *cps;
13115 {
13116 #if ATTENTION
13117     if (!cps->useSigint) return;
13118     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13119     switch (gameMode) {
13120       case MachinePlaysWhite:
13121       case MachinePlaysBlack:
13122       case TwoMachinesPlay:
13123       case IcsPlayingWhite:
13124       case IcsPlayingBlack:
13125       case AnalyzeMode:
13126       case AnalyzeFile:
13127         /* Skip if we know it isn't thinking */
13128         if (!cps->maybeThinking) return;
13129         if (appData.debugMode)
13130           fprintf(debugFP, "Interrupting %s\n", cps->which);
13131         InterruptChildProcess(cps->pr);
13132         cps->maybeThinking = FALSE;
13133         break;
13134       default:
13135         break;
13136     }
13137 #endif /*ATTENTION*/
13138 }
13139
13140 int
13141 CheckFlags()
13142 {
13143     if (whiteTimeRemaining <= 0) {
13144         if (!whiteFlag) {
13145             whiteFlag = TRUE;
13146             if (appData.icsActive) {
13147                 if (appData.autoCallFlag &&
13148                     gameMode == IcsPlayingBlack && !blackFlag) {
13149                   SendToICS(ics_prefix);
13150                   SendToICS("flag\n");
13151                 }
13152             } else {
13153                 if (blackFlag) {
13154                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13155                 } else {
13156                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13157                     if (appData.autoCallFlag) {
13158                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13159                         return TRUE;
13160                     }
13161                 }
13162             }
13163         }
13164     }
13165     if (blackTimeRemaining <= 0) {
13166         if (!blackFlag) {
13167             blackFlag = TRUE;
13168             if (appData.icsActive) {
13169                 if (appData.autoCallFlag &&
13170                     gameMode == IcsPlayingWhite && !whiteFlag) {
13171                   SendToICS(ics_prefix);
13172                   SendToICS("flag\n");
13173                 }
13174             } else {
13175                 if (whiteFlag) {
13176                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13177                 } else {
13178                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13179                     if (appData.autoCallFlag) {
13180                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13181                         return TRUE;
13182                     }
13183                 }
13184             }
13185         }
13186     }
13187     return FALSE;
13188 }
13189
13190 void
13191 CheckTimeControl()
13192 {
13193     if (!appData.clockMode || appData.icsActive ||
13194         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13195
13196     /*
13197      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13198      */
13199     if ( !WhiteOnMove(forwardMostMove) )
13200         /* White made time control */
13201         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13202         /* [HGM] time odds: correct new time quota for time odds! */
13203                                             / WhitePlayer()->timeOdds;
13204       else
13205         /* Black made time control */
13206         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13207                                             / WhitePlayer()->other->timeOdds;
13208 }
13209
13210 void
13211 DisplayBothClocks()
13212 {
13213     int wom = gameMode == EditPosition ?
13214       !blackPlaysFirst : WhiteOnMove(currentMove);
13215     DisplayWhiteClock(whiteTimeRemaining, wom);
13216     DisplayBlackClock(blackTimeRemaining, !wom);
13217 }
13218
13219
13220 /* Timekeeping seems to be a portability nightmare.  I think everyone
13221    has ftime(), but I'm really not sure, so I'm including some ifdefs
13222    to use other calls if you don't.  Clocks will be less accurate if
13223    you have neither ftime nor gettimeofday.
13224 */
13225
13226 /* VS 2008 requires the #include outside of the function */
13227 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13228 #include <sys/timeb.h>
13229 #endif
13230
13231 /* Get the current time as a TimeMark */
13232 void
13233 GetTimeMark(tm)
13234      TimeMark *tm;
13235 {
13236 #if HAVE_GETTIMEOFDAY
13237
13238     struct timeval timeVal;
13239     struct timezone timeZone;
13240
13241     gettimeofday(&timeVal, &timeZone);
13242     tm->sec = (long) timeVal.tv_sec; 
13243     tm->ms = (int) (timeVal.tv_usec / 1000L);
13244
13245 #else /*!HAVE_GETTIMEOFDAY*/
13246 #if HAVE_FTIME
13247
13248 // include <sys/timeb.h> / moved to just above start of function
13249     struct timeb timeB;
13250
13251     ftime(&timeB);
13252     tm->sec = (long) timeB.time;
13253     tm->ms = (int) timeB.millitm;
13254
13255 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13256     tm->sec = (long) time(NULL);
13257     tm->ms = 0;
13258 #endif
13259 #endif
13260 }
13261
13262 /* Return the difference in milliseconds between two
13263    time marks.  We assume the difference will fit in a long!
13264 */
13265 long
13266 SubtractTimeMarks(tm2, tm1)
13267      TimeMark *tm2, *tm1;
13268 {
13269     return 1000L*(tm2->sec - tm1->sec) +
13270            (long) (tm2->ms - tm1->ms);
13271 }
13272
13273
13274 /*
13275  * Code to manage the game clocks.
13276  *
13277  * In tournament play, black starts the clock and then white makes a move.
13278  * We give the human user a slight advantage if he is playing white---the
13279  * clocks don't run until he makes his first move, so it takes zero time.
13280  * Also, we don't account for network lag, so we could get out of sync
13281  * with GNU Chess's clock -- but then, referees are always right.  
13282  */
13283
13284 static TimeMark tickStartTM;
13285 static long intendedTickLength;
13286
13287 long
13288 NextTickLength(timeRemaining)
13289      long timeRemaining;
13290 {
13291     long nominalTickLength, nextTickLength;
13292
13293     if (timeRemaining > 0L && timeRemaining <= 10000L)
13294       nominalTickLength = 100L;
13295     else
13296       nominalTickLength = 1000L;
13297     nextTickLength = timeRemaining % nominalTickLength;
13298     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13299
13300     return nextTickLength;
13301 }
13302
13303 /* Adjust clock one minute up or down */
13304 void
13305 AdjustClock(Boolean which, int dir)
13306 {
13307     if(which) blackTimeRemaining += 60000*dir;
13308     else      whiteTimeRemaining += 60000*dir;
13309     DisplayBothClocks();
13310 }
13311
13312 /* Stop clocks and reset to a fresh time control */
13313 void
13314 ResetClocks() 
13315 {
13316     (void) StopClockTimer();
13317     if (appData.icsActive) {
13318         whiteTimeRemaining = blackTimeRemaining = 0;
13319     } else { /* [HGM] correct new time quote for time odds */
13320         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13321         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13322     }
13323     if (whiteFlag || blackFlag) {
13324         DisplayTitle("");
13325         whiteFlag = blackFlag = FALSE;
13326     }
13327     DisplayBothClocks();
13328 }
13329
13330 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13331
13332 /* Decrement running clock by amount of time that has passed */
13333 void
13334 DecrementClocks()
13335 {
13336     long timeRemaining;
13337     long lastTickLength, fudge;
13338     TimeMark now;
13339
13340     if (!appData.clockMode) return;
13341     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13342         
13343     GetTimeMark(&now);
13344
13345     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13346
13347     /* Fudge if we woke up a little too soon */
13348     fudge = intendedTickLength - lastTickLength;
13349     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13350
13351     if (WhiteOnMove(forwardMostMove)) {
13352         if(whiteNPS >= 0) lastTickLength = 0;
13353         timeRemaining = whiteTimeRemaining -= lastTickLength;
13354         DisplayWhiteClock(whiteTimeRemaining - fudge,
13355                           WhiteOnMove(currentMove));
13356     } else {
13357         if(blackNPS >= 0) lastTickLength = 0;
13358         timeRemaining = blackTimeRemaining -= lastTickLength;
13359         DisplayBlackClock(blackTimeRemaining - fudge,
13360                           !WhiteOnMove(currentMove));
13361     }
13362
13363     if (CheckFlags()) return;
13364         
13365     tickStartTM = now;
13366     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13367     StartClockTimer(intendedTickLength);
13368
13369     /* if the time remaining has fallen below the alarm threshold, sound the
13370      * alarm. if the alarm has sounded and (due to a takeback or time control
13371      * with increment) the time remaining has increased to a level above the
13372      * threshold, reset the alarm so it can sound again. 
13373      */
13374     
13375     if (appData.icsActive && appData.icsAlarm) {
13376
13377         /* make sure we are dealing with the user's clock */
13378         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13379                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13380            )) return;
13381
13382         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13383             alarmSounded = FALSE;
13384         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13385             PlayAlarmSound();
13386             alarmSounded = TRUE;
13387         }
13388     }
13389 }
13390
13391
13392 /* A player has just moved, so stop the previously running
13393    clock and (if in clock mode) start the other one.
13394    We redisplay both clocks in case we're in ICS mode, because
13395    ICS gives us an update to both clocks after every move.
13396    Note that this routine is called *after* forwardMostMove
13397    is updated, so the last fractional tick must be subtracted
13398    from the color that is *not* on move now.
13399 */
13400 void
13401 SwitchClocks(int newMoveNr)
13402 {
13403     long lastTickLength;
13404     TimeMark now;
13405     int flagged = FALSE;
13406
13407     GetTimeMark(&now);
13408
13409     if (StopClockTimer() && appData.clockMode) {
13410         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13411         if (!WhiteOnMove(forwardMostMove)) {
13412             if(blackNPS >= 0) lastTickLength = 0;
13413             blackTimeRemaining -= lastTickLength;
13414            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13415 //         if(pvInfoList[forwardMostMove-1].time == -1)
13416                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13417                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13418         } else {
13419            if(whiteNPS >= 0) lastTickLength = 0;
13420            whiteTimeRemaining -= lastTickLength;
13421            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13422 //         if(pvInfoList[forwardMostMove-1].time == -1)
13423                  pvInfoList[forwardMostMove-1].time = 
13424                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13425         }
13426         flagged = CheckFlags();
13427     }
13428     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
13429     CheckTimeControl();
13430
13431     if (flagged || !appData.clockMode) return;
13432
13433     switch (gameMode) {
13434       case MachinePlaysBlack:
13435       case MachinePlaysWhite:
13436       case BeginningOfGame:
13437         if (pausing) return;
13438         break;
13439
13440       case EditGame:
13441       case PlayFromGameFile:
13442       case IcsExamining:
13443         return;
13444
13445       default:
13446         break;
13447     }
13448
13449     tickStartTM = now;
13450     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13451       whiteTimeRemaining : blackTimeRemaining);
13452     StartClockTimer(intendedTickLength);
13453 }
13454         
13455
13456 /* Stop both clocks */
13457 void
13458 StopClocks()
13459 {       
13460     long lastTickLength;
13461     TimeMark now;
13462
13463     if (!StopClockTimer()) return;
13464     if (!appData.clockMode) return;
13465
13466     GetTimeMark(&now);
13467
13468     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13469     if (WhiteOnMove(forwardMostMove)) {
13470         if(whiteNPS >= 0) lastTickLength = 0;
13471         whiteTimeRemaining -= lastTickLength;
13472         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13473     } else {
13474         if(blackNPS >= 0) lastTickLength = 0;
13475         blackTimeRemaining -= lastTickLength;
13476         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13477     }
13478     CheckFlags();
13479 }
13480         
13481 /* Start clock of player on move.  Time may have been reset, so
13482    if clock is already running, stop and restart it. */
13483 void
13484 StartClocks()
13485 {
13486     (void) StopClockTimer(); /* in case it was running already */
13487     DisplayBothClocks();
13488     if (CheckFlags()) return;
13489
13490     if (!appData.clockMode) return;
13491     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13492
13493     GetTimeMark(&tickStartTM);
13494     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13495       whiteTimeRemaining : blackTimeRemaining);
13496
13497    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13498     whiteNPS = blackNPS = -1; 
13499     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13500        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13501         whiteNPS = first.nps;
13502     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13503        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13504         blackNPS = first.nps;
13505     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13506         whiteNPS = second.nps;
13507     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13508         blackNPS = second.nps;
13509     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13510
13511     StartClockTimer(intendedTickLength);
13512 }
13513
13514 char *
13515 TimeString(ms)
13516      long ms;
13517 {
13518     long second, minute, hour, day;
13519     char *sign = "";
13520     static char buf[32];
13521     
13522     if (ms > 0 && ms <= 9900) {
13523       /* convert milliseconds to tenths, rounding up */
13524       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13525
13526       sprintf(buf, " %03.1f ", tenths/10.0);
13527       return buf;
13528     }
13529
13530     /* convert milliseconds to seconds, rounding up */
13531     /* use floating point to avoid strangeness of integer division
13532        with negative dividends on many machines */
13533     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13534
13535     if (second < 0) {
13536         sign = "-";
13537         second = -second;
13538     }
13539     
13540     day = second / (60 * 60 * 24);
13541     second = second % (60 * 60 * 24);
13542     hour = second / (60 * 60);
13543     second = second % (60 * 60);
13544     minute = second / 60;
13545     second = second % 60;
13546     
13547     if (day > 0)
13548       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13549               sign, day, hour, minute, second);
13550     else if (hour > 0)
13551       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13552     else
13553       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13554     
13555     return buf;
13556 }
13557
13558
13559 /*
13560  * This is necessary because some C libraries aren't ANSI C compliant yet.
13561  */
13562 char *
13563 StrStr(string, match)
13564      char *string, *match;
13565 {
13566     int i, length;
13567     
13568     length = strlen(match);
13569     
13570     for (i = strlen(string) - length; i >= 0; i--, string++)
13571       if (!strncmp(match, string, length))
13572         return string;
13573     
13574     return NULL;
13575 }
13576
13577 char *
13578 StrCaseStr(string, match)
13579      char *string, *match;
13580 {
13581     int i, j, length;
13582     
13583     length = strlen(match);
13584     
13585     for (i = strlen(string) - length; i >= 0; i--, string++) {
13586         for (j = 0; j < length; j++) {
13587             if (ToLower(match[j]) != ToLower(string[j]))
13588               break;
13589         }
13590         if (j == length) return string;
13591     }
13592
13593     return NULL;
13594 }
13595
13596 #ifndef _amigados
13597 int
13598 StrCaseCmp(s1, s2)
13599      char *s1, *s2;
13600 {
13601     char c1, c2;
13602     
13603     for (;;) {
13604         c1 = ToLower(*s1++);
13605         c2 = ToLower(*s2++);
13606         if (c1 > c2) return 1;
13607         if (c1 < c2) return -1;
13608         if (c1 == NULLCHAR) return 0;
13609     }
13610 }
13611
13612
13613 int
13614 ToLower(c)
13615      int c;
13616 {
13617     return isupper(c) ? tolower(c) : c;
13618 }
13619
13620
13621 int
13622 ToUpper(c)
13623      int c;
13624 {
13625     return islower(c) ? toupper(c) : c;
13626 }
13627 #endif /* !_amigados    */
13628
13629 char *
13630 StrSave(s)
13631      char *s;
13632 {
13633     char *ret;
13634
13635     if ((ret = (char *) malloc(strlen(s) + 1))) {
13636         strcpy(ret, s);
13637     }
13638     return ret;
13639 }
13640
13641 char *
13642 StrSavePtr(s, savePtr)
13643      char *s, **savePtr;
13644 {
13645     if (*savePtr) {
13646         free(*savePtr);
13647     }
13648     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13649         strcpy(*savePtr, s);
13650     }
13651     return(*savePtr);
13652 }
13653
13654 char *
13655 PGNDate()
13656 {
13657     time_t clock;
13658     struct tm *tm;
13659     char buf[MSG_SIZ];
13660
13661     clock = time((time_t *)NULL);
13662     tm = localtime(&clock);
13663     sprintf(buf, "%04d.%02d.%02d",
13664             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13665     return StrSave(buf);
13666 }
13667
13668
13669 char *
13670 PositionToFEN(move, overrideCastling)
13671      int move;
13672      char *overrideCastling;
13673 {
13674     int i, j, fromX, fromY, toX, toY;
13675     int whiteToPlay;
13676     char buf[128];
13677     char *p, *q;
13678     int emptycount;
13679     ChessSquare piece;
13680
13681     whiteToPlay = (gameMode == EditPosition) ?
13682       !blackPlaysFirst : (move % 2 == 0);
13683     p = buf;
13684
13685     /* Piece placement data */
13686     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13687         emptycount = 0;
13688         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13689             if (boards[move][i][j] == EmptySquare) {
13690                 emptycount++;
13691             } else { ChessSquare piece = boards[move][i][j];
13692                 if (emptycount > 0) {
13693                     if(emptycount<10) /* [HGM] can be >= 10 */
13694                         *p++ = '0' + emptycount;
13695                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13696                     emptycount = 0;
13697                 }
13698                 if(PieceToChar(piece) == '+') {
13699                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13700                     *p++ = '+';
13701                     piece = (ChessSquare)(DEMOTED piece);
13702                 } 
13703                 *p++ = PieceToChar(piece);
13704                 if(p[-1] == '~') {
13705                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13706                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13707                     *p++ = '~';
13708                 }
13709             }
13710         }
13711         if (emptycount > 0) {
13712             if(emptycount<10) /* [HGM] can be >= 10 */
13713                 *p++ = '0' + emptycount;
13714             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13715             emptycount = 0;
13716         }
13717         *p++ = '/';
13718     }
13719     *(p - 1) = ' ';
13720
13721     /* [HGM] print Crazyhouse or Shogi holdings */
13722     if( gameInfo.holdingsWidth ) {
13723         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13724         q = p;
13725         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13726             piece = boards[move][i][BOARD_WIDTH-1];
13727             if( piece != EmptySquare )
13728               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13729                   *p++ = PieceToChar(piece);
13730         }
13731         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13732             piece = boards[move][BOARD_HEIGHT-i-1][0];
13733             if( piece != EmptySquare )
13734               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13735                   *p++ = PieceToChar(piece);
13736         }
13737
13738         if( q == p ) *p++ = '-';
13739         *p++ = ']';
13740         *p++ = ' ';
13741     }
13742
13743     /* Active color */
13744     *p++ = whiteToPlay ? 'w' : 'b';
13745     *p++ = ' ';
13746
13747   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13748     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13749   } else {
13750   if(nrCastlingRights) {
13751      q = p;
13752      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13753        /* [HGM] write directly from rights */
13754            if(castlingRights[move][2] >= 0 &&
13755               castlingRights[move][0] >= 0   )
13756                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13757            if(castlingRights[move][2] >= 0 &&
13758               castlingRights[move][1] >= 0   )
13759                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13760            if(castlingRights[move][5] >= 0 &&
13761               castlingRights[move][3] >= 0   )
13762                 *p++ = castlingRights[move][3] + AAA;
13763            if(castlingRights[move][5] >= 0 &&
13764               castlingRights[move][4] >= 0   )
13765                 *p++ = castlingRights[move][4] + AAA;
13766      } else {
13767
13768         /* [HGM] write true castling rights */
13769         if( nrCastlingRights == 6 ) {
13770             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13771                castlingRights[move][2] >= 0  ) *p++ = 'K';
13772             if(castlingRights[move][1] == BOARD_LEFT &&
13773                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13774             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13775                castlingRights[move][5] >= 0  ) *p++ = 'k';
13776             if(castlingRights[move][4] == BOARD_LEFT &&
13777                castlingRights[move][5] >= 0  ) *p++ = 'q';
13778         }
13779      }
13780      if (q == p) *p++ = '-'; /* No castling rights */
13781      *p++ = ' ';
13782   }
13783
13784   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13785      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
13786     /* En passant target square */
13787     if (move > backwardMostMove) {
13788         fromX = moveList[move - 1][0] - AAA;
13789         fromY = moveList[move - 1][1] - ONE;
13790         toX = moveList[move - 1][2] - AAA;
13791         toY = moveList[move - 1][3] - ONE;
13792         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13793             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13794             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13795             fromX == toX) {
13796             /* 2-square pawn move just happened */
13797             *p++ = toX + AAA;
13798             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13799         } else {
13800             *p++ = '-';
13801         }
13802     } else if(move == backwardMostMove) {
13803         // [HGM] perhaps we should always do it like this, and forget the above?
13804         if(epStatus[move] >= 0) {
13805             *p++ = epStatus[move] + AAA;
13806             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13807         } else {
13808             *p++ = '-';
13809         }
13810     } else {
13811         *p++ = '-';
13812     }
13813     *p++ = ' ';
13814   }
13815   }
13816
13817     /* [HGM] find reversible plies */
13818     {   int i = 0, j=move;
13819
13820         if (appData.debugMode) { int k;
13821             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13822             for(k=backwardMostMove; k<=forwardMostMove; k++)
13823                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13824
13825         }
13826
13827         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13828         if( j == backwardMostMove ) i += initialRulePlies;
13829         sprintf(p, "%d ", i);
13830         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13831     }
13832     /* Fullmove number */
13833     sprintf(p, "%d", (move / 2) + 1);
13834     
13835     return StrSave(buf);
13836 }
13837
13838 Boolean
13839 ParseFEN(board, blackPlaysFirst, fen)
13840     Board board;
13841      int *blackPlaysFirst;
13842      char *fen;
13843 {
13844     int i, j;
13845     char *p;
13846     int emptycount;
13847     ChessSquare piece;
13848
13849     p = fen;
13850
13851     /* [HGM] by default clear Crazyhouse holdings, if present */
13852     if(gameInfo.holdingsWidth) {
13853        for(i=0; i<BOARD_HEIGHT; i++) {
13854            board[i][0]             = EmptySquare; /* black holdings */
13855            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13856            board[i][1]             = (ChessSquare) 0; /* black counts */
13857            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13858        }
13859     }
13860
13861     /* Piece placement data */
13862     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13863         j = 0;
13864         for (;;) {
13865             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13866                 if (*p == '/') p++;
13867                 emptycount = gameInfo.boardWidth - j;
13868                 while (emptycount--)
13869                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13870                 break;
13871 #if(BOARD_SIZE >= 10)
13872             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13873                 p++; emptycount=10;
13874                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13875                 while (emptycount--)
13876                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13877 #endif
13878             } else if (isdigit(*p)) {
13879                 emptycount = *p++ - '0';
13880                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13881                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13882                 while (emptycount--)
13883                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13884             } else if (*p == '+' || isalpha(*p)) {
13885                 if (j >= gameInfo.boardWidth) return FALSE;
13886                 if(*p=='+') {
13887                     piece = CharToPiece(*++p);
13888                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13889                     piece = (ChessSquare) (PROMOTED piece ); p++;
13890                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13891                 } else piece = CharToPiece(*p++);
13892
13893                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13894                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13895                     piece = (ChessSquare) (PROMOTED piece);
13896                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13897                     p++;
13898                 }
13899                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13900             } else {
13901                 return FALSE;
13902             }
13903         }
13904     }
13905     while (*p == '/' || *p == ' ') p++;
13906
13907     /* [HGM] look for Crazyhouse holdings here */
13908     while(*p==' ') p++;
13909     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13910         if(*p == '[') p++;
13911         if(*p == '-' ) *p++; /* empty holdings */ else {
13912             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13913             /* if we would allow FEN reading to set board size, we would   */
13914             /* have to add holdings and shift the board read so far here   */
13915             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13916                 *p++;
13917                 if((int) piece >= (int) BlackPawn ) {
13918                     i = (int)piece - (int)BlackPawn;
13919                     i = PieceToNumber((ChessSquare)i);
13920                     if( i >= gameInfo.holdingsSize ) return FALSE;
13921                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13922                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13923                 } else {
13924                     i = (int)piece - (int)WhitePawn;
13925                     i = PieceToNumber((ChessSquare)i);
13926                     if( i >= gameInfo.holdingsSize ) return FALSE;
13927                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13928                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13929                 }
13930             }
13931         }
13932         if(*p == ']') *p++;
13933     }
13934
13935     while(*p == ' ') p++;
13936
13937     /* Active color */
13938     switch (*p++) {
13939       case 'w':
13940         *blackPlaysFirst = FALSE;
13941         break;
13942       case 'b': 
13943         *blackPlaysFirst = TRUE;
13944         break;
13945       default:
13946         return FALSE;
13947     }
13948
13949     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13950     /* return the extra info in global variiables             */
13951
13952     /* set defaults in case FEN is incomplete */
13953     FENepStatus = EP_UNKNOWN;
13954     for(i=0; i<nrCastlingRights; i++ ) {
13955         FENcastlingRights[i] =
13956             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13957     }   /* assume possible unless obviously impossible */
13958     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13959     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13960     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13961                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13962     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13963     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13964     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13965                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13966     FENrulePlies = 0;
13967
13968     while(*p==' ') p++;
13969     if(nrCastlingRights) {
13970       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13971           /* castling indicator present, so default becomes no castlings */
13972           for(i=0; i<nrCastlingRights; i++ ) {
13973                  FENcastlingRights[i] = -1;
13974           }
13975       }
13976       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13977              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13978              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13979              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13980         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13981
13982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13983             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13984             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13985         }
13986         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13987             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13988         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13989                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13990         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13991                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13992         switch(c) {
13993           case'K':
13994               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13995               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13996               FENcastlingRights[2] = whiteKingFile;
13997               break;
13998           case'Q':
13999               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14000               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14001               FENcastlingRights[2] = whiteKingFile;
14002               break;
14003           case'k':
14004               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
14005               FENcastlingRights[3] = i != blackKingFile ? i : -1;
14006               FENcastlingRights[5] = blackKingFile;
14007               break;
14008           case'q':
14009               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14010               FENcastlingRights[4] = i != blackKingFile ? i : -1;
14011               FENcastlingRights[5] = blackKingFile;
14012           case '-':
14013               break;
14014           default: /* FRC castlings */
14015               if(c >= 'a') { /* black rights */
14016                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14017                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14018                   if(i == BOARD_RGHT) break;
14019                   FENcastlingRights[5] = i;
14020                   c -= AAA;
14021                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14022                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14023                   if(c > i)
14024                       FENcastlingRights[3] = c;
14025                   else
14026                       FENcastlingRights[4] = c;
14027               } else { /* white rights */
14028                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14029                     if(board[0][i] == WhiteKing) break;
14030                   if(i == BOARD_RGHT) break;
14031                   FENcastlingRights[2] = i;
14032                   c -= AAA - 'a' + 'A';
14033                   if(board[0][c] >= WhiteKing) break;
14034                   if(c > i)
14035                       FENcastlingRights[0] = c;
14036                   else
14037                       FENcastlingRights[1] = c;
14038               }
14039         }
14040       }
14041       for(i=0; i<nrCastlingRights; i++)
14042         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
14043     if (appData.debugMode) {
14044         fprintf(debugFP, "FEN castling rights:");
14045         for(i=0; i<nrCastlingRights; i++)
14046         fprintf(debugFP, " %d", FENcastlingRights[i]);
14047         fprintf(debugFP, "\n");
14048     }
14049
14050       while(*p==' ') p++;
14051     }
14052
14053     /* read e.p. field in games that know e.p. capture */
14054     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14055        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14056       if(*p=='-') {
14057         p++; FENepStatus = EP_NONE;
14058       } else {
14059          char c = *p++ - AAA;
14060
14061          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14062          if(*p >= '0' && *p <='9') *p++;
14063          FENepStatus = c;
14064       }
14065     }
14066
14067
14068     if(sscanf(p, "%d", &i) == 1) {
14069         FENrulePlies = i; /* 50-move ply counter */
14070         /* (The move number is still ignored)    */
14071     }
14072
14073     return TRUE;
14074 }
14075       
14076 void
14077 EditPositionPasteFEN(char *fen)
14078 {
14079   if (fen != NULL) {
14080     Board initial_position;
14081
14082     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14083       DisplayError(_("Bad FEN position in clipboard"), 0);
14084       return ;
14085     } else {
14086       int savedBlackPlaysFirst = blackPlaysFirst;
14087       EditPositionEvent();
14088       blackPlaysFirst = savedBlackPlaysFirst;
14089       CopyBoard(boards[0], initial_position);
14090           /* [HGM] copy FEN attributes as well */
14091           {   int i;
14092               initialRulePlies = FENrulePlies;
14093               epStatus[0] = FENepStatus;
14094               for( i=0; i<nrCastlingRights; i++ )
14095                   castlingRights[0][i] = FENcastlingRights[i];
14096           }
14097       EditPositionDone(FALSE);
14098       DisplayBothClocks();
14099       DrawPosition(FALSE, boards[currentMove]);
14100     }
14101   }
14102 }
14103
14104 static char cseq[12] = "\\   ";
14105
14106 Boolean set_cont_sequence(char *new_seq)
14107 {
14108     int len;
14109     Boolean ret;
14110
14111     // handle bad attempts to set the sequence
14112         if (!new_seq)
14113                 return 0; // acceptable error - no debug
14114
14115     len = strlen(new_seq);
14116     ret = (len > 0) && (len < sizeof(cseq));
14117     if (ret)
14118         strcpy(cseq, new_seq);
14119     else if (appData.debugMode)
14120         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14121     return ret;
14122 }
14123
14124 /*
14125     reformat a source message so words don't cross the width boundary.  internal
14126     newlines are not removed.  returns the wrapped size (no null character unless
14127     included in source message).  If dest is NULL, only calculate the size required
14128     for the dest buffer.  lp argument indicats line position upon entry, and it's
14129     passed back upon exit.
14130 */
14131 int wrap(char *dest, char *src, int count, int width, int *lp)
14132 {
14133     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14134
14135     cseq_len = strlen(cseq);
14136     old_line = line = *lp;
14137     ansi = len = clen = 0;
14138
14139     for (i=0; i < count; i++)
14140     {
14141         if (src[i] == '\033')
14142             ansi = 1;
14143
14144         // if we hit the width, back up
14145         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14146         {
14147             // store i & len in case the word is too long
14148             old_i = i, old_len = len;
14149
14150             // find the end of the last word
14151             while (i && src[i] != ' ' && src[i] != '\n')
14152             {
14153                 i--;
14154                 len--;
14155             }
14156
14157             // word too long?  restore i & len before splitting it
14158             if ((old_i-i+clen) >= width)
14159             {
14160                 i = old_i;
14161                 len = old_len;
14162             }
14163
14164             // extra space?
14165             if (i && src[i-1] == ' ')
14166                 len--;
14167
14168             if (src[i] != ' ' && src[i] != '\n')
14169             {
14170                 i--;
14171                 if (len)
14172                     len--;
14173             }
14174
14175             // now append the newline and continuation sequence
14176             if (dest)
14177                 dest[len] = '\n';
14178             len++;
14179             if (dest)
14180                 strncpy(dest+len, cseq, cseq_len);
14181             len += cseq_len;
14182             line = cseq_len;
14183             clen = cseq_len;
14184             continue;
14185         }
14186
14187         if (dest)
14188             dest[len] = src[i];
14189         len++;
14190         if (!ansi)
14191             line++;
14192         if (src[i] == '\n')
14193             line = 0;
14194         if (src[i] == 'm')
14195             ansi = 0;
14196     }
14197     if (dest && appData.debugMode)
14198     {
14199         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14200             count, width, line, len, *lp);
14201         show_bytes(debugFP, src, count);
14202         fprintf(debugFP, "\ndest: ");
14203         show_bytes(debugFP, dest, len);
14204         fprintf(debugFP, "\n");
14205     }
14206     *lp = dest ? line : old_line;
14207
14208     return len;
14209 }