added missing library for build on OS X
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # 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((void));
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 StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
799         appData.clockMode = FALSE;
800         first.sendTime = second.sendTime = 0;
801     }
802     
803 #if ZIPPY
804     /* Override some settings from environment variables, for backward
805        compatibility.  Unfortunately it's not feasible to have the env
806        vars just set defaults, at least in xboard.  Ugh.
807     */
808     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
809       ZippyInit();
810     }
811 #endif
812     
813     if (appData.noChessProgram) {
814         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815         sprintf(programVersion, "%s", PACKAGE_STRING);
816     } else {
817       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
820     }
821
822     if (!appData.icsActive) {
823       char buf[MSG_SIZ];
824       /* Check for variants that are supported only in ICS mode,
825          or not at all.  Some that are accepted here nevertheless
826          have bugs; see comments below.
827       */
828       VariantClass variant = StringToVariant(appData.variant);
829       switch (variant) {
830       case VariantBughouse:     /* need four players and two boards */
831       case VariantKriegspiel:   /* need to hide pieces and move details */
832       /* case VariantFischeRandom: (Fabien: moved below) */
833         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834         DisplayFatalError(buf, 0, 2);
835         return;
836
837       case VariantUnknown:
838       case VariantLoadable:
839       case Variant29:
840       case Variant30:
841       case Variant31:
842       case Variant32:
843       case Variant33:
844       case Variant34:
845       case Variant35:
846       case Variant36:
847       default:
848         sprintf(buf, _("Unknown variant name %s"), appData.variant);
849         DisplayFatalError(buf, 0, 2);
850         return;
851
852       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
853       case VariantFairy:      /* [HGM] TestLegality definitely off! */
854       case VariantGothic:     /* [HGM] should work */
855       case VariantCapablanca: /* [HGM] should work */
856       case VariantCourier:    /* [HGM] initial forced moves not implemented */
857       case VariantShogi:      /* [HGM] drops not tested for legality */
858       case VariantKnightmate: /* [HGM] should work */
859       case VariantCylinder:   /* [HGM] untested */
860       case VariantFalcon:     /* [HGM] untested */
861       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862                                  offboard interposition not understood */
863       case VariantNormal:     /* definitely works! */
864       case VariantWildCastle: /* pieces not automatically shuffled */
865       case VariantNoCastle:   /* pieces not automatically shuffled */
866       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867       case VariantLosers:     /* should work except for win condition,
868                                  and doesn't know captures are mandatory */
869       case VariantSuicide:    /* should work except for win condition,
870                                  and doesn't know captures are mandatory */
871       case VariantGiveaway:   /* should work except for win condition,
872                                  and doesn't know captures are mandatory */
873       case VariantTwoKings:   /* should work */
874       case VariantAtomic:     /* should work except for win condition */
875       case Variant3Check:     /* should work except for win condition */
876       case VariantShatranj:   /* should work except for all win conditions */
877       case VariantBerolina:   /* might work if TestLegality is off */
878       case VariantCapaRandom: /* should work */
879       case VariantJanus:      /* should work */
880       case VariantSuper:      /* experimental */
881       case VariantGreat:      /* experimental, requires legality testing to be off */
882         break;
883       }
884     }
885
886     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
887     InitEngineUCI( installDir, &second );
888 }
889
890 int NextIntegerFromString( char ** str, long * value )
891 {
892     int result = -1;
893     char * s = *str;
894
895     while( *s == ' ' || *s == '\t' ) {
896         s++;
897     }
898
899     *value = 0;
900
901     if( *s >= '0' && *s <= '9' ) {
902         while( *s >= '0' && *s <= '9' ) {
903             *value = *value * 10 + (*s - '0');
904             s++;
905         }
906
907         result = 0;
908     }
909
910     *str = s;
911
912     return result;
913 }
914
915 int NextTimeControlFromString( char ** str, long * value )
916 {
917     long temp;
918     int result = NextIntegerFromString( str, &temp );
919
920     if( result == 0 ) {
921         *value = temp * 60; /* Minutes */
922         if( **str == ':' ) {
923             (*str)++;
924             result = NextIntegerFromString( str, &temp );
925             *value += temp; /* Seconds */
926         }
927     }
928
929     return result;
930 }
931
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
934     int result = -1; long temp, temp2;
935
936     if(**str != '+') return -1; // old params remain in force!
937     (*str)++;
938     if( NextTimeControlFromString( str, &temp ) ) return -1;
939
940     if(**str != '/') {
941         /* time only: incremental or sudden-death time control */
942         if(**str == '+') { /* increment follows; read it */
943             (*str)++;
944             if(result = NextIntegerFromString( str, &temp2)) return -1;
945             *inc = temp2 * 1000;
946         } else *inc = 0;
947         *moves = 0; *tc = temp * 1000; 
948         return 0;
949     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
950
951     (*str)++; /* classical time control */
952     result = NextTimeControlFromString( str, &temp2);
953     if(result == 0) {
954         *moves = temp/60;
955         *tc    = temp2 * 1000;
956         *inc   = 0;
957     }
958     return result;
959 }
960
961 int GetTimeQuota(int movenr)
962 {   /* [HGM] get time to add from the multi-session time-control string */
963     int moves=1; /* kludge to force reading of first session */
964     long time, increment;
965     char *s = fullTimeControlString;
966
967     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968     do {
969         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971         if(movenr == -1) return time;    /* last move before new session     */
972         if(!moves) return increment;     /* current session is incremental   */
973         if(movenr >= 0) movenr -= moves; /* we already finished this session */
974     } while(movenr >= -1);               /* try again for next session       */
975
976     return 0; // no new time quota on this move
977 }
978
979 int
980 ParseTimeControl(tc, ti, mps)
981      char *tc;
982      int ti;
983      int mps;
984 {
985   long tc1;
986   long tc2;
987   char buf[MSG_SIZ];
988   
989   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
990   if(ti > 0) {
991     if(mps)
992       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993     else sprintf(buf, "+%s+%d", tc, ti);
994   } else {
995     if(mps)
996              sprintf(buf, "+%d/%s", mps, tc);
997     else sprintf(buf, "+%s", tc);
998   }
999   fullTimeControlString = StrSave(buf);
1000   
1001   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1002     return FALSE;
1003   }
1004   
1005   if( *tc == '/' ) {
1006     /* Parse second time control */
1007     tc++;
1008     
1009     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1010       return FALSE;
1011     }
1012     
1013     if( tc2 == 0 ) {
1014       return FALSE;
1015     }
1016     
1017     timeControl_2 = tc2 * 1000;
1018   }
1019   else {
1020     timeControl_2 = 0;
1021   }
1022   
1023   if( tc1 == 0 ) {
1024     return FALSE;
1025   }
1026   
1027   timeControl = tc1 * 1000;
1028   
1029   if (ti >= 0) {
1030     timeIncrement = ti * 1000;  /* convert to ms */
1031     movesPerSession = 0;
1032   } else {
1033     timeIncrement = 0;
1034     movesPerSession = mps;
1035   }
1036   return TRUE;
1037 }
1038
1039 void
1040 InitBackEnd2()
1041 {
1042     if (appData.debugMode) {
1043         fprintf(debugFP, "%s\n", programVersion);
1044     }
1045
1046     set_cont_sequence(appData.wrapContSeq);
1047     if (appData.matchGames > 0) {
1048         appData.matchMode = TRUE;
1049     } else if (appData.matchMode) {
1050         appData.matchGames = 1;
1051     }
1052     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1053         appData.matchGames = appData.sameColorGames;
1054     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1055         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1056         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1057     }
1058     Reset(TRUE, FALSE);
1059     if (appData.noChessProgram || first.protocolVersion == 1) {
1060       InitBackEnd3();
1061     } else {
1062       /* kludge: allow timeout for initial "feature" commands */
1063       FreezeUI();
1064       DisplayMessage("", _("Starting chess program"));
1065       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1066     }
1067 }
1068
1069 void
1070 InitBackEnd3 P((void))
1071 {
1072     GameMode initialMode;
1073     char buf[MSG_SIZ];
1074     int err;
1075
1076     InitChessProgram(&first, startedFromSetupPosition);
1077
1078
1079     if (appData.icsActive) {
1080 #ifdef WIN32
1081         /* [DM] Make a console window if needed [HGM] merged ifs */
1082         ConsoleCreate(); 
1083 #endif
1084         err = establish();
1085         if (err != 0) {
1086             if (*appData.icsCommPort != NULLCHAR) {
1087                 sprintf(buf, _("Could not open comm port %s"),  
1088                         appData.icsCommPort);
1089             } else {
1090                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1091                         appData.icsHost, appData.icsPort);
1092             }
1093             DisplayFatalError(buf, err, 1);
1094             return;
1095         }
1096         SetICSMode();
1097         telnetISR =
1098           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099         fromUserISR =
1100           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1101     } else if (appData.noChessProgram) {
1102         SetNCPMode();
1103     } else {
1104         SetGNUMode();
1105     }
1106
1107     if (*appData.cmailGameName != NULLCHAR) {
1108         SetCmailMode();
1109         OpenLoopback(&cmailPR);
1110         cmailISR =
1111           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1112     }
1113     
1114     ThawUI();
1115     DisplayMessage("", "");
1116     if (StrCaseCmp(appData.initialMode, "") == 0) {
1117       initialMode = BeginningOfGame;
1118     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1119       initialMode = TwoMachinesPlay;
1120     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1121       initialMode = AnalyzeFile; 
1122     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1123       initialMode = AnalyzeMode;
1124     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1125       initialMode = MachinePlaysWhite;
1126     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1127       initialMode = MachinePlaysBlack;
1128     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1129       initialMode = EditGame;
1130     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1131       initialMode = EditPosition;
1132     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1133       initialMode = Training;
1134     } else {
1135       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1136       DisplayFatalError(buf, 0, 2);
1137       return;
1138     }
1139
1140     if (appData.matchMode) {
1141         /* Set up machine vs. machine match */
1142         if (appData.noChessProgram) {
1143             DisplayFatalError(_("Can't have a match with no chess programs"),
1144                               0, 2);
1145             return;
1146         }
1147         matchMode = TRUE;
1148         matchGame = 1;
1149         if (*appData.loadGameFile != NULLCHAR) {
1150             int index = appData.loadGameIndex; // [HGM] autoinc
1151             if(index<0) lastIndex = index = 1;
1152             if (!LoadGameFromFile(appData.loadGameFile,
1153                                   index,
1154                                   appData.loadGameFile, FALSE)) {
1155                 DisplayFatalError(_("Bad game file"), 0, 1);
1156                 return;
1157             }
1158         } else if (*appData.loadPositionFile != NULLCHAR) {
1159             int index = appData.loadPositionIndex; // [HGM] autoinc
1160             if(index<0) lastIndex = index = 1;
1161             if (!LoadPositionFromFile(appData.loadPositionFile,
1162                                       index,
1163                                       appData.loadPositionFile)) {
1164                 DisplayFatalError(_("Bad position file"), 0, 1);
1165                 return;
1166             }
1167         }
1168         TwoMachinesEvent();
1169     } else if (*appData.cmailGameName != NULLCHAR) {
1170         /* Set up cmail mode */
1171         ReloadCmailMsgEvent(TRUE);
1172     } else {
1173         /* Set up other modes */
1174         if (initialMode == AnalyzeFile) {
1175           if (*appData.loadGameFile == NULLCHAR) {
1176             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1177             return;
1178           }
1179         }
1180         if (*appData.loadGameFile != NULLCHAR) {
1181             (void) LoadGameFromFile(appData.loadGameFile,
1182                                     appData.loadGameIndex,
1183                                     appData.loadGameFile, TRUE);
1184         } else if (*appData.loadPositionFile != NULLCHAR) {
1185             (void) LoadPositionFromFile(appData.loadPositionFile,
1186                                         appData.loadPositionIndex,
1187                                         appData.loadPositionFile);
1188             /* [HGM] try to make self-starting even after FEN load */
1189             /* to allow automatic setup of fairy variants with wtm */
1190             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1191                 gameMode = BeginningOfGame;
1192                 setboardSpoiledMachineBlack = 1;
1193             }
1194             /* [HGM] loadPos: make that every new game uses the setup */
1195             /* from file as long as we do not switch variant          */
1196             if(!blackPlaysFirst) { int i;
1197                 startedFromPositionFile = TRUE;
1198                 CopyBoard(filePosition, boards[0]);
1199                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1200             }
1201         }
1202         if (initialMode == AnalyzeMode) {
1203           if (appData.noChessProgram) {
1204             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1205             return;
1206           }
1207           if (appData.icsActive) {
1208             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1209             return;
1210           }
1211           AnalyzeModeEvent();
1212         } else if (initialMode == AnalyzeFile) {
1213           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1214           ShowThinkingEvent();
1215           AnalyzeFileEvent();
1216           AnalysisPeriodicEvent(1);
1217         } else if (initialMode == MachinePlaysWhite) {
1218           if (appData.noChessProgram) {
1219             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1220                               0, 2);
1221             return;
1222           }
1223           if (appData.icsActive) {
1224             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1225                               0, 2);
1226             return;
1227           }
1228           MachineWhiteEvent();
1229         } else if (initialMode == MachinePlaysBlack) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1232                               0, 2);
1233             return;
1234           }
1235           if (appData.icsActive) {
1236             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1237                               0, 2);
1238             return;
1239           }
1240           MachineBlackEvent();
1241         } else if (initialMode == TwoMachinesPlay) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1244                               0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1249                               0, 2);
1250             return;
1251           }
1252           TwoMachinesEvent();
1253         } else if (initialMode == EditGame) {
1254           EditGameEvent();
1255         } else if (initialMode == EditPosition) {
1256           EditPositionEvent();
1257         } else if (initialMode == Training) {
1258           if (*appData.loadGameFile == NULLCHAR) {
1259             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1260             return;
1261           }
1262           TrainingEvent();
1263         }
1264     }
1265 }
1266
1267 /*
1268  * Establish will establish a contact to a remote host.port.
1269  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1270  *  used to talk to the host.
1271  * Returns 0 if okay, error code if not.
1272  */
1273 int
1274 establish()
1275 {
1276     char buf[MSG_SIZ];
1277
1278     if (*appData.icsCommPort != NULLCHAR) {
1279         /* Talk to the host through a serial comm port */
1280         return OpenCommPort(appData.icsCommPort, &icsPR);
1281
1282     } else if (*appData.gateway != NULLCHAR) {
1283         if (*appData.remoteShell == NULLCHAR) {
1284             /* Use the rcmd protocol to run telnet program on a gateway host */
1285             snprintf(buf, sizeof(buf), "%s %s %s",
1286                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1287             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1288
1289         } else {
1290             /* Use the rsh program to run telnet program on a gateway host */
1291             if (*appData.remoteUser == NULLCHAR) {
1292                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1293                         appData.gateway, appData.telnetProgram,
1294                         appData.icsHost, appData.icsPort);
1295             } else {
1296                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1297                         appData.remoteShell, appData.gateway, 
1298                         appData.remoteUser, appData.telnetProgram,
1299                         appData.icsHost, appData.icsPort);
1300             }
1301             return StartChildProcess(buf, "", &icsPR);
1302
1303         }
1304     } else if (appData.useTelnet) {
1305         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1306
1307     } else {
1308         /* TCP socket interface differs somewhat between
1309            Unix and NT; handle details in the front end.
1310            */
1311         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1312     }
1313 }
1314
1315 void
1316 show_bytes(fp, buf, count)
1317      FILE *fp;
1318      char *buf;
1319      int count;
1320 {
1321     while (count--) {
1322         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1323             fprintf(fp, "\\%03o", *buf & 0xff);
1324         } else {
1325             putc(*buf, fp);
1326         }
1327         buf++;
1328     }
1329     fflush(fp);
1330 }
1331
1332 /* Returns an errno value */
1333 int
1334 OutputMaybeTelnet(pr, message, count, outError)
1335      ProcRef pr;
1336      char *message;
1337      int count;
1338      int *outError;
1339 {
1340     char buf[8192], *p, *q, *buflim;
1341     int left, newcount, outcount;
1342
1343     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1344         *appData.gateway != NULLCHAR) {
1345         if (appData.debugMode) {
1346             fprintf(debugFP, ">ICS: ");
1347             show_bytes(debugFP, message, count);
1348             fprintf(debugFP, "\n");
1349         }
1350         return OutputToProcess(pr, message, count, outError);
1351     }
1352
1353     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1354     p = message;
1355     q = buf;
1356     left = count;
1357     newcount = 0;
1358     while (left) {
1359         if (q >= buflim) {
1360             if (appData.debugMode) {
1361                 fprintf(debugFP, ">ICS: ");
1362                 show_bytes(debugFP, buf, newcount);
1363                 fprintf(debugFP, "\n");
1364             }
1365             outcount = OutputToProcess(pr, buf, newcount, outError);
1366             if (outcount < newcount) return -1; /* to be sure */
1367             q = buf;
1368             newcount = 0;
1369         }
1370         if (*p == '\n') {
1371             *q++ = '\r';
1372             newcount++;
1373         } else if (((unsigned char) *p) == TN_IAC) {
1374             *q++ = (char) TN_IAC;
1375             newcount ++;
1376         }
1377         *q++ = *p++;
1378         newcount++;
1379         left--;
1380     }
1381     if (appData.debugMode) {
1382         fprintf(debugFP, ">ICS: ");
1383         show_bytes(debugFP, buf, newcount);
1384         fprintf(debugFP, "\n");
1385     }
1386     outcount = OutputToProcess(pr, buf, newcount, outError);
1387     if (outcount < newcount) return -1; /* to be sure */
1388     return count;
1389 }
1390
1391 void
1392 read_from_player(isr, closure, message, count, error)
1393      InputSourceRef isr;
1394      VOIDSTAR closure;
1395      char *message;
1396      int count;
1397      int error;
1398 {
1399     int outError, outCount;
1400     static int gotEof = 0;
1401
1402     /* Pass data read from player on to ICS */
1403     if (count > 0) {
1404         gotEof = 0;
1405         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1406         if (outCount < count) {
1407             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408         }
1409     } else if (count < 0) {
1410         RemoveInputSource(isr);
1411         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1412     } else if (gotEof++ > 0) {
1413         RemoveInputSource(isr);
1414         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1415     }
1416 }
1417
1418 void
1419 KeepAlive()
1420 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1421     SendToICS("date\n");
1422     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1423 }
1424
1425 /* added routine for printf style output to ics */
1426 void ics_printf(char *format, ...)
1427 {
1428     char buffer[MSG_SIZ];
1429     va_list args;
1430
1431     va_start(args, format);
1432     vsnprintf(buffer, sizeof(buffer), format, args);
1433     buffer[sizeof(buffer)-1] = '\0';
1434     SendToICS(buffer);
1435     va_end(args);
1436 }
1437
1438 void
1439 SendToICS(s)
1440      char *s;
1441 {
1442     int count, outCount, outError;
1443
1444     if (icsPR == NULL) return;
1445
1446     count = strlen(s);
1447     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1448     if (outCount < count) {
1449         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1450     }
1451 }
1452
1453 /* This is used for sending logon scripts to the ICS. Sending
1454    without a delay causes problems when using timestamp on ICC
1455    (at least on my machine). */
1456 void
1457 SendToICSDelayed(s,msdelay)
1458      char *s;
1459      long msdelay;
1460 {
1461     int count, outCount, outError;
1462
1463     if (icsPR == NULL) return;
1464
1465     count = strlen(s);
1466     if (appData.debugMode) {
1467         fprintf(debugFP, ">ICS: ");
1468         show_bytes(debugFP, s, count);
1469         fprintf(debugFP, "\n");
1470     }
1471     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472                                       msdelay);
1473     if (outCount < count) {
1474         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1475     }
1476 }
1477
1478
1479 /* Remove all highlighting escape sequences in s
1480    Also deletes any suffix starting with '(' 
1481    */
1482 char *
1483 StripHighlightAndTitle(s)
1484      char *s;
1485 {
1486     static char retbuf[MSG_SIZ];
1487     char *p = retbuf;
1488
1489     while (*s != NULLCHAR) {
1490         while (*s == '\033') {
1491             while (*s != NULLCHAR && !isalpha(*s)) s++;
1492             if (*s != NULLCHAR) s++;
1493         }
1494         while (*s != NULLCHAR && *s != '\033') {
1495             if (*s == '(' || *s == '[') {
1496                 *p = NULLCHAR;
1497                 return retbuf;
1498             }
1499             *p++ = *s++;
1500         }
1501     }
1502     *p = NULLCHAR;
1503     return retbuf;
1504 }
1505
1506 /* Remove all highlighting escape sequences in s */
1507 char *
1508 StripHighlight(s)
1509      char *s;
1510 {
1511     static char retbuf[MSG_SIZ];
1512     char *p = retbuf;
1513
1514     while (*s != NULLCHAR) {
1515         while (*s == '\033') {
1516             while (*s != NULLCHAR && !isalpha(*s)) s++;
1517             if (*s != NULLCHAR) s++;
1518         }
1519         while (*s != NULLCHAR && *s != '\033') {
1520             *p++ = *s++;
1521         }
1522     }
1523     *p = NULLCHAR;
1524     return retbuf;
1525 }
1526
1527 char *variantNames[] = VARIANT_NAMES;
1528 char *
1529 VariantName(v)
1530      VariantClass v;
1531 {
1532     return variantNames[v];
1533 }
1534
1535
1536 /* Identify a variant from the strings the chess servers use or the
1537    PGN Variant tag names we use. */
1538 VariantClass
1539 StringToVariant(e)
1540      char *e;
1541 {
1542     char *p;
1543     int wnum = -1;
1544     VariantClass v = VariantNormal;
1545     int i, found = FALSE;
1546     char buf[MSG_SIZ];
1547
1548     if (!e) return v;
1549
1550     /* [HGM] skip over optional board-size prefixes */
1551     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1552         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1553         while( *e++ != '_');
1554     }
1555
1556     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1557         v = VariantNormal;
1558         found = TRUE;
1559     } else
1560     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1561       if (StrCaseStr(e, variantNames[i])) {
1562         v = (VariantClass) i;
1563         found = TRUE;
1564         break;
1565       }
1566     }
1567
1568     if (!found) {
1569       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1570           || StrCaseStr(e, "wild/fr") 
1571           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1572         v = VariantFischeRandom;
1573       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1574                  (i = 1, p = StrCaseStr(e, "w"))) {
1575         p += i;
1576         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1577         if (isdigit(*p)) {
1578           wnum = atoi(p);
1579         } else {
1580           wnum = -1;
1581         }
1582         switch (wnum) {
1583         case 0: /* FICS only, actually */
1584         case 1:
1585           /* Castling legal even if K starts on d-file */
1586           v = VariantWildCastle;
1587           break;
1588         case 2:
1589         case 3:
1590         case 4:
1591           /* Castling illegal even if K & R happen to start in
1592              normal positions. */
1593           v = VariantNoCastle;
1594           break;
1595         case 5:
1596         case 7:
1597         case 8:
1598         case 10:
1599         case 11:
1600         case 12:
1601         case 13:
1602         case 14:
1603         case 15:
1604         case 18:
1605         case 19:
1606           /* Castling legal iff K & R start in normal positions */
1607           v = VariantNormal;
1608           break;
1609         case 6:
1610         case 20:
1611         case 21:
1612           /* Special wilds for position setup; unclear what to do here */
1613           v = VariantLoadable;
1614           break;
1615         case 9:
1616           /* Bizarre ICC game */
1617           v = VariantTwoKings;
1618           break;
1619         case 16:
1620           v = VariantKriegspiel;
1621           break;
1622         case 17:
1623           v = VariantLosers;
1624           break;
1625         case 22:
1626           v = VariantFischeRandom;
1627           break;
1628         case 23:
1629           v = VariantCrazyhouse;
1630           break;
1631         case 24:
1632           v = VariantBughouse;
1633           break;
1634         case 25:
1635           v = Variant3Check;
1636           break;
1637         case 26:
1638           /* Not quite the same as FICS suicide! */
1639           v = VariantGiveaway;
1640           break;
1641         case 27:
1642           v = VariantAtomic;
1643           break;
1644         case 28:
1645           v = VariantShatranj;
1646           break;
1647
1648         /* Temporary names for future ICC types.  The name *will* change in 
1649            the next xboard/WinBoard release after ICC defines it. */
1650         case 29:
1651           v = Variant29;
1652           break;
1653         case 30:
1654           v = Variant30;
1655           break;
1656         case 31:
1657           v = Variant31;
1658           break;
1659         case 32:
1660           v = Variant32;
1661           break;
1662         case 33:
1663           v = Variant33;
1664           break;
1665         case 34:
1666           v = Variant34;
1667           break;
1668         case 35:
1669           v = Variant35;
1670           break;
1671         case 36:
1672           v = Variant36;
1673           break;
1674         case 37:
1675           v = VariantShogi;
1676           break;
1677         case 38:
1678           v = VariantXiangqi;
1679           break;
1680         case 39:
1681           v = VariantCourier;
1682           break;
1683         case 40:
1684           v = VariantGothic;
1685           break;
1686         case 41:
1687           v = VariantCapablanca;
1688           break;
1689         case 42:
1690           v = VariantKnightmate;
1691           break;
1692         case 43:
1693           v = VariantFairy;
1694           break;
1695         case 44:
1696           v = VariantCylinder;
1697           break;
1698         case 45:
1699           v = VariantFalcon;
1700           break;
1701         case 46:
1702           v = VariantCapaRandom;
1703           break;
1704         case 47:
1705           v = VariantBerolina;
1706           break;
1707         case 48:
1708           v = VariantJanus;
1709           break;
1710         case 49:
1711           v = VariantSuper;
1712           break;
1713         case 50:
1714           v = VariantGreat;
1715           break;
1716         case -1:
1717           /* Found "wild" or "w" in the string but no number;
1718              must assume it's normal chess. */
1719           v = VariantNormal;
1720           break;
1721         default:
1722           sprintf(buf, _("Unknown wild type %d"), wnum);
1723           DisplayError(buf, 0);
1724           v = VariantUnknown;
1725           break;
1726         }
1727       }
1728     }
1729     if (appData.debugMode) {
1730       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1731               e, wnum, VariantName(v));
1732     }
1733     return v;
1734 }
1735
1736 static int leftover_start = 0, leftover_len = 0;
1737 char star_match[STAR_MATCH_N][MSG_SIZ];
1738
1739 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1740    advance *index beyond it, and set leftover_start to the new value of
1741    *index; else return FALSE.  If pattern contains the character '*', it
1742    matches any sequence of characters not containing '\r', '\n', or the
1743    character following the '*' (if any), and the matched sequence(s) are
1744    copied into star_match.
1745    */
1746 int
1747 looking_at(buf, index, pattern)
1748      char *buf;
1749      int *index;
1750      char *pattern;
1751 {
1752     char *bufp = &buf[*index], *patternp = pattern;
1753     int star_count = 0;
1754     char *matchp = star_match[0];
1755     
1756     for (;;) {
1757         if (*patternp == NULLCHAR) {
1758             *index = leftover_start = bufp - buf;
1759             *matchp = NULLCHAR;
1760             return TRUE;
1761         }
1762         if (*bufp == NULLCHAR) return FALSE;
1763         if (*patternp == '*') {
1764             if (*bufp == *(patternp + 1)) {
1765                 *matchp = NULLCHAR;
1766                 matchp = star_match[++star_count];
1767                 patternp += 2;
1768                 bufp++;
1769                 continue;
1770             } else if (*bufp == '\n' || *bufp == '\r') {
1771                 patternp++;
1772                 if (*patternp == NULLCHAR)
1773                   continue;
1774                 else
1775                   return FALSE;
1776             } else {
1777                 *matchp++ = *bufp++;
1778                 continue;
1779             }
1780         }
1781         if (*patternp != *bufp) return FALSE;
1782         patternp++;
1783         bufp++;
1784     }
1785 }
1786
1787 void
1788 SendToPlayer(data, length)
1789      char *data;
1790      int length;
1791 {
1792     int error, outCount;
1793     outCount = OutputToProcess(NoProc, data, length, &error);
1794     if (outCount < length) {
1795         DisplayFatalError(_("Error writing to display"), error, 1);
1796     }
1797 }
1798
1799 void
1800 PackHolding(packed, holding)
1801      char packed[];
1802      char *holding;
1803 {
1804     char *p = holding;
1805     char *q = packed;
1806     int runlength = 0;
1807     int curr = 9999;
1808     do {
1809         if (*p == curr) {
1810             runlength++;
1811         } else {
1812             switch (runlength) {
1813               case 0:
1814                 break;
1815               case 1:
1816                 *q++ = curr;
1817                 break;
1818               case 2:
1819                 *q++ = curr;
1820                 *q++ = curr;
1821                 break;
1822               default:
1823                 sprintf(q, "%d", runlength);
1824                 while (*q) q++;
1825                 *q++ = curr;
1826                 break;
1827             }
1828             runlength = 1;
1829             curr = *p;
1830         }
1831     } while (*p++);
1832     *q = NULLCHAR;
1833 }
1834
1835 /* Telnet protocol requests from the front end */
1836 void
1837 TelnetRequest(ddww, option)
1838      unsigned char ddww, option;
1839 {
1840     unsigned char msg[3];
1841     int outCount, outError;
1842
1843     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844
1845     if (appData.debugMode) {
1846         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1847         switch (ddww) {
1848           case TN_DO:
1849             ddwwStr = "DO";
1850             break;
1851           case TN_DONT:
1852             ddwwStr = "DONT";
1853             break;
1854           case TN_WILL:
1855             ddwwStr = "WILL";
1856             break;
1857           case TN_WONT:
1858             ddwwStr = "WONT";
1859             break;
1860           default:
1861             ddwwStr = buf1;
1862             sprintf(buf1, "%d", ddww);
1863             break;
1864         }
1865         switch (option) {
1866           case TN_ECHO:
1867             optionStr = "ECHO";
1868             break;
1869           default:
1870             optionStr = buf2;
1871             sprintf(buf2, "%d", option);
1872             break;
1873         }
1874         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1875     }
1876     msg[0] = TN_IAC;
1877     msg[1] = ddww;
1878     msg[2] = option;
1879     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880     if (outCount < 3) {
1881         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1882     }
1883 }
1884
1885 void
1886 DoEcho()
1887 {
1888     if (!appData.icsActive) return;
1889     TelnetRequest(TN_DO, TN_ECHO);
1890 }
1891
1892 void
1893 DontEcho()
1894 {
1895     if (!appData.icsActive) return;
1896     TelnetRequest(TN_DONT, TN_ECHO);
1897 }
1898
1899 void
1900 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 {
1902     /* put the holdings sent to us by the server on the board holdings area */
1903     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1904     char p;
1905     ChessSquare piece;
1906
1907     if(gameInfo.holdingsWidth < 2)  return;
1908     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1909         return; // prevent overwriting by pre-board holdings
1910
1911     if( (int)lowestPiece >= BlackPawn ) {
1912         holdingsColumn = 0;
1913         countsColumn = 1;
1914         holdingsStartRow = BOARD_HEIGHT-1;
1915         direction = -1;
1916     } else {
1917         holdingsColumn = BOARD_WIDTH-1;
1918         countsColumn = BOARD_WIDTH-2;
1919         holdingsStartRow = 0;
1920         direction = 1;
1921     }
1922
1923     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1924         board[i][holdingsColumn] = EmptySquare;
1925         board[i][countsColumn]   = (ChessSquare) 0;
1926     }
1927     while( (p=*holdings++) != NULLCHAR ) {
1928         piece = CharToPiece( ToUpper(p) );
1929         if(piece == EmptySquare) continue;
1930         /*j = (int) piece - (int) WhitePawn;*/
1931         j = PieceToNumber(piece);
1932         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1933         if(j < 0) continue;               /* should not happen */
1934         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1935         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1936         board[holdingsStartRow+j*direction][countsColumn]++;
1937     }
1938 }
1939
1940
1941 void
1942 VariantSwitch(Board board, VariantClass newVariant)
1943 {
1944    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1945    Board oldBoard;
1946
1947    startedFromPositionFile = FALSE;
1948    if(gameInfo.variant == newVariant) return;
1949
1950    /* [HGM] This routine is called each time an assignment is made to
1951     * gameInfo.variant during a game, to make sure the board sizes
1952     * are set to match the new variant. If that means adding or deleting
1953     * holdings, we shift the playing board accordingly
1954     * This kludge is needed because in ICS observe mode, we get boards
1955     * of an ongoing game without knowing the variant, and learn about the
1956     * latter only later. This can be because of the move list we requested,
1957     * in which case the game history is refilled from the beginning anyway,
1958     * but also when receiving holdings of a crazyhouse game. In the latter
1959     * case we want to add those holdings to the already received position.
1960     */
1961
1962    
1963    if (appData.debugMode) {
1964      fprintf(debugFP, "Switch board from %s to %s\n",
1965              VariantName(gameInfo.variant), VariantName(newVariant));
1966      setbuf(debugFP, NULL);
1967    }
1968    shuffleOpenings = 0;       /* [HGM] shuffle */
1969    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1970    switch(newVariant) 
1971      {
1972      case VariantShogi:
1973        newWidth = 9;  newHeight = 9;
1974        gameInfo.holdingsSize = 7;
1975      case VariantBughouse:
1976      case VariantCrazyhouse:
1977        newHoldingsWidth = 2; break;
1978      case VariantGreat:
1979        newWidth = 10;
1980      case VariantSuper:
1981        newHoldingsWidth = 2;
1982        gameInfo.holdingsSize = 8;
1983        break;
1984      case VariantGothic:
1985      case VariantCapablanca:
1986      case VariantCapaRandom:
1987        newWidth = 10;
1988      default:
1989        newHoldingsWidth = gameInfo.holdingsSize = 0;
1990      };
1991    
1992    if(newWidth  != gameInfo.boardWidth  ||
1993       newHeight != gameInfo.boardHeight ||
1994       newHoldingsWidth != gameInfo.holdingsWidth ) {
1995      
1996      /* shift position to new playing area, if needed */
1997      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1998        for(i=0; i<BOARD_HEIGHT; i++) 
1999          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2000            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2001              board[i][j];
2002        for(i=0; i<newHeight; i++) {
2003          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2004          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2005        }
2006      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2007        for(i=0; i<BOARD_HEIGHT; i++)
2008          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2009            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2010              board[i][j];
2011      }
2012      gameInfo.boardWidth  = newWidth;
2013      gameInfo.boardHeight = newHeight;
2014      gameInfo.holdingsWidth = newHoldingsWidth;
2015      gameInfo.variant = newVariant;
2016      InitDrawingSizes(-2, 0);
2017    } else gameInfo.variant = newVariant;
2018    CopyBoard(oldBoard, board);   // remember correctly formatted board
2019      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2020    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2021 }
2022
2023 static int loggedOn = FALSE;
2024
2025 /*-- Game start info cache: --*/
2026 int gs_gamenum;
2027 char gs_kind[MSG_SIZ];
2028 static char player1Name[128] = "";
2029 static char player2Name[128] = "";
2030 static char cont_seq[] = "\n\\   ";
2031 static int player1Rating = -1;
2032 static int player2Rating = -1;
2033 /*----------------------------*/
2034
2035 ColorClass curColor = ColorNormal;
2036 int suppressKibitz = 0;
2037
2038 void
2039 read_from_ics(isr, closure, data, count, error)
2040      InputSourceRef isr;
2041      VOIDSTAR closure;
2042      char *data;
2043      int count;
2044      int error;
2045 {
2046 #define BUF_SIZE 8192
2047 #define STARTED_NONE 0
2048 #define STARTED_MOVES 1
2049 #define STARTED_BOARD 2
2050 #define STARTED_OBSERVE 3
2051 #define STARTED_HOLDINGS 4
2052 #define STARTED_CHATTER 5
2053 #define STARTED_COMMENT 6
2054 #define STARTED_MOVES_NOHIDE 7
2055     
2056     static int started = STARTED_NONE;
2057     static char parse[20000];
2058     static int parse_pos = 0;
2059     static char buf[BUF_SIZE + 1];
2060     static int firstTime = TRUE, intfSet = FALSE;
2061     static ColorClass prevColor = ColorNormal;
2062     static int savingComment = FALSE;
2063     static int cmatch = 0; // continuation sequence match
2064     char *bp;
2065     char str[500];
2066     int i, oldi;
2067     int buf_len;
2068     int next_out;
2069     int tkind;
2070     int backup;    /* [DM] For zippy color lines */
2071     char *p;
2072     char talker[MSG_SIZ]; // [HGM] chat
2073     int channel;
2074
2075     if (appData.debugMode) {
2076       if (!error) {
2077         fprintf(debugFP, "<ICS: ");
2078         show_bytes(debugFP, data, count);
2079         fprintf(debugFP, "\n");
2080       }
2081     }
2082
2083     if (appData.debugMode) { int f = forwardMostMove;
2084         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2085                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2086     }
2087     if (count > 0) {
2088         /* If last read ended with a partial line that we couldn't parse,
2089            prepend it to the new read and try again. */
2090         if (leftover_len > 0) {
2091             for (i=0; i<leftover_len; i++)
2092               buf[i] = buf[leftover_start + i];
2093         }
2094
2095     /* copy new characters into the buffer */
2096     bp = buf + leftover_len;
2097     buf_len=leftover_len;
2098     for (i=0; i<count; i++)
2099     {
2100         // ignore these
2101         if (data[i] == '\r')
2102             continue;
2103
2104         // join lines split by ICS?
2105         if (!appData.noJoin)
2106         {
2107             /*
2108                 Joining just consists of finding matches against the
2109                 continuation sequence, and discarding that sequence
2110                 if found instead of copying it.  So, until a match
2111                 fails, there's nothing to do since it might be the
2112                 complete sequence, and thus, something we don't want
2113                 copied.
2114             */
2115             if (data[i] == cont_seq[cmatch])
2116             {
2117                 cmatch++;
2118                 if (cmatch == strlen(cont_seq))
2119                 {
2120                     cmatch = 0; // complete match.  just reset the counter
2121
2122                     /*
2123                         it's possible for the ICS to not include the space
2124                         at the end of the last word, making our [correct]
2125                         join operation fuse two separate words.  the server
2126                         does this when the space occurs at the width setting.
2127                     */
2128                     if (!buf_len || buf[buf_len-1] != ' ')
2129                     {
2130                         *bp++ = ' ';
2131                         buf_len++;
2132                     }
2133                 }
2134                 continue;
2135             }
2136             else if (cmatch)
2137             {
2138                 /*
2139                     match failed, so we have to copy what matched before
2140                     falling through and copying this character.  In reality,
2141                     this will only ever be just the newline character, but
2142                     it doesn't hurt to be precise.
2143                 */
2144                 strncpy(bp, cont_seq, cmatch);
2145                 bp += cmatch;
2146                 buf_len += cmatch;
2147                 cmatch = 0;
2148             }
2149         }
2150
2151         // copy this char
2152         *bp++ = data[i];
2153         buf_len++;
2154     }
2155
2156         buf[buf_len] = NULLCHAR;
2157         next_out = leftover_len;
2158         leftover_start = 0;
2159         
2160         i = 0;
2161         while (i < buf_len) {
2162             /* Deal with part of the TELNET option negotiation
2163                protocol.  We refuse to do anything beyond the
2164                defaults, except that we allow the WILL ECHO option,
2165                which ICS uses to turn off password echoing when we are
2166                directly connected to it.  We reject this option
2167                if localLineEditing mode is on (always on in xboard)
2168                and we are talking to port 23, which might be a real
2169                telnet server that will try to keep WILL ECHO on permanently.
2170              */
2171             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2172                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2173                 unsigned char option;
2174                 oldi = i;
2175                 switch ((unsigned char) buf[++i]) {
2176                   case TN_WILL:
2177                     if (appData.debugMode)
2178                       fprintf(debugFP, "\n<WILL ");
2179                     switch (option = (unsigned char) buf[++i]) {
2180                       case TN_ECHO:
2181                         if (appData.debugMode)
2182                           fprintf(debugFP, "ECHO ");
2183                         /* Reply only if this is a change, according
2184                            to the protocol rules. */
2185                         if (remoteEchoOption) break;
2186                         if (appData.localLineEditing &&
2187                             atoi(appData.icsPort) == TN_PORT) {
2188                             TelnetRequest(TN_DONT, TN_ECHO);
2189                         } else {
2190                             EchoOff();
2191                             TelnetRequest(TN_DO, TN_ECHO);
2192                             remoteEchoOption = TRUE;
2193                         }
2194                         break;
2195                       default:
2196                         if (appData.debugMode)
2197                           fprintf(debugFP, "%d ", option);
2198                         /* Whatever this is, we don't want it. */
2199                         TelnetRequest(TN_DONT, option);
2200                         break;
2201                     }
2202                     break;
2203                   case TN_WONT:
2204                     if (appData.debugMode)
2205                       fprintf(debugFP, "\n<WONT ");
2206                     switch (option = (unsigned char) buf[++i]) {
2207                       case TN_ECHO:
2208                         if (appData.debugMode)
2209                           fprintf(debugFP, "ECHO ");
2210                         /* Reply only if this is a change, according
2211                            to the protocol rules. */
2212                         if (!remoteEchoOption) break;
2213                         EchoOn();
2214                         TelnetRequest(TN_DONT, TN_ECHO);
2215                         remoteEchoOption = FALSE;
2216                         break;
2217                       default:
2218                         if (appData.debugMode)
2219                           fprintf(debugFP, "%d ", (unsigned char) option);
2220                         /* Whatever this is, it must already be turned
2221                            off, because we never agree to turn on
2222                            anything non-default, so according to the
2223                            protocol rules, we don't reply. */
2224                         break;
2225                     }
2226                     break;
2227                   case TN_DO:
2228                     if (appData.debugMode)
2229                       fprintf(debugFP, "\n<DO ");
2230                     switch (option = (unsigned char) buf[++i]) {
2231                       default:
2232                         /* Whatever this is, we refuse to do it. */
2233                         if (appData.debugMode)
2234                           fprintf(debugFP, "%d ", option);
2235                         TelnetRequest(TN_WONT, option);
2236                         break;
2237                     }
2238                     break;
2239                   case TN_DONT:
2240                     if (appData.debugMode)
2241                       fprintf(debugFP, "\n<DONT ");
2242                     switch (option = (unsigned char) buf[++i]) {
2243                       default:
2244                         if (appData.debugMode)
2245                           fprintf(debugFP, "%d ", option);
2246                         /* Whatever this is, we are already not doing
2247                            it, because we never agree to do anything
2248                            non-default, so according to the protocol
2249                            rules, we don't reply. */
2250                         break;
2251                     }
2252                     break;
2253                   case TN_IAC:
2254                     if (appData.debugMode)
2255                       fprintf(debugFP, "\n<IAC ");
2256                     /* Doubled IAC; pass it through */
2257                     i--;
2258                     break;
2259                   default:
2260                     if (appData.debugMode)
2261                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2262                     /* Drop all other telnet commands on the floor */
2263                     break;
2264                 }
2265                 if (oldi > next_out)
2266                   SendToPlayer(&buf[next_out], oldi - next_out);
2267                 if (++i > next_out)
2268                   next_out = i;
2269                 continue;
2270             }
2271                 
2272             /* OK, this at least will *usually* work */
2273             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2274                 loggedOn = TRUE;
2275             }
2276             
2277             if (loggedOn && !intfSet) {
2278                 if (ics_type == ICS_ICC) {
2279                   sprintf(str,
2280                           "/set-quietly interface %s\n/set-quietly style 12\n",
2281                           programVersion);
2282                 } else if (ics_type == ICS_CHESSNET) {
2283                   sprintf(str, "/style 12\n");
2284                 } else {
2285                   strcpy(str, "alias $ @\n$set interface ");
2286                   strcat(str, programVersion);
2287                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2288 #ifdef WIN32
2289                   strcat(str, "$iset nohighlight 1\n");
2290 #endif
2291                   strcat(str, "$iset lock 1\n$style 12\n");
2292                 }
2293                 SendToICS(str);
2294                 NotifyFrontendLogin();
2295                 intfSet = TRUE;
2296             }
2297
2298             if (started == STARTED_COMMENT) {
2299                 /* Accumulate characters in comment */
2300                 parse[parse_pos++] = buf[i];
2301                 if (buf[i] == '\n') {
2302                     parse[parse_pos] = NULLCHAR;
2303                     if(chattingPartner>=0) {
2304                         char mess[MSG_SIZ];
2305                         sprintf(mess, "%s%s", talker, parse);
2306                         OutputChatMessage(chattingPartner, mess);
2307                         chattingPartner = -1;
2308                     } else
2309                     if(!suppressKibitz) // [HGM] kibitz
2310                         AppendComment(forwardMostMove, StripHighlight(parse));
2311                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2312                         int nrDigit = 0, nrAlph = 0, i;
2313                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2314                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2315                         parse[parse_pos] = NULLCHAR;
2316                         // try to be smart: if it does not look like search info, it should go to
2317                         // ICS interaction window after all, not to engine-output window.
2318                         for(i=0; i<parse_pos; i++) { // count letters and digits
2319                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2320                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2321                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2322                         }
2323                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2324                             int depth=0; float score;
2325                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2326                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2327                                 pvInfoList[forwardMostMove-1].depth = depth;
2328                                 pvInfoList[forwardMostMove-1].score = 100*score;
2329                             }
2330                             OutputKibitz(suppressKibitz, parse);
2331                         } else {
2332                             char tmp[MSG_SIZ];
2333                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2334                             SendToPlayer(tmp, strlen(tmp));
2335                         }
2336                     }
2337                     started = STARTED_NONE;
2338                 } else {
2339                     /* Don't match patterns against characters in chatter */
2340                     i++;
2341                     continue;
2342                 }
2343             }
2344             if (started == STARTED_CHATTER) {
2345                 if (buf[i] != '\n') {
2346                     /* Don't match patterns against characters in chatter */
2347                     i++;
2348                     continue;
2349                 }
2350                 started = STARTED_NONE;
2351             }
2352
2353             /* Kludge to deal with rcmd protocol */
2354             if (firstTime && looking_at(buf, &i, "\001*")) {
2355                 DisplayFatalError(&buf[1], 0, 1);
2356                 continue;
2357             } else {
2358                 firstTime = FALSE;
2359             }
2360
2361             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2362                 ics_type = ICS_ICC;
2363                 ics_prefix = "/";
2364                 if (appData.debugMode)
2365                   fprintf(debugFP, "ics_type %d\n", ics_type);
2366                 continue;
2367             }
2368             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2369                 ics_type = ICS_FICS;
2370                 ics_prefix = "$";
2371                 if (appData.debugMode)
2372                   fprintf(debugFP, "ics_type %d\n", ics_type);
2373                 continue;
2374             }
2375             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2376                 ics_type = ICS_CHESSNET;
2377                 ics_prefix = "/";
2378                 if (appData.debugMode)
2379                   fprintf(debugFP, "ics_type %d\n", ics_type);
2380                 continue;
2381             }
2382
2383             if (!loggedOn &&
2384                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2385                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2386                  looking_at(buf, &i, "will be \"*\""))) {
2387               strcpy(ics_handle, star_match[0]);
2388               continue;
2389             }
2390
2391             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2392               char buf[MSG_SIZ];
2393               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2394               DisplayIcsInteractionTitle(buf);
2395               have_set_title = TRUE;
2396             }
2397
2398             /* skip finger notes */
2399             if (started == STARTED_NONE &&
2400                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2401                  (buf[i] == '1' && buf[i+1] == '0')) &&
2402                 buf[i+2] == ':' && buf[i+3] == ' ') {
2403               started = STARTED_CHATTER;
2404               i += 3;
2405               continue;
2406             }
2407
2408             /* skip formula vars */
2409             if (started == STARTED_NONE &&
2410                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2411               started = STARTED_CHATTER;
2412               i += 3;
2413               continue;
2414             }
2415
2416             oldi = i;
2417             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2418             if (appData.autoKibitz && started == STARTED_NONE && 
2419                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2420                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2421                 if(looking_at(buf, &i, "* kibitzes: ") &&
2422                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2423                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2424                         suppressKibitz = TRUE;
2425                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2426                                 && (gameMode == IcsPlayingWhite)) ||
2427                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2428                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2429                             started = STARTED_CHATTER; // own kibitz we simply discard
2430                         else {
2431                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2432                             parse_pos = 0; parse[0] = NULLCHAR;
2433                             savingComment = TRUE;
2434                             suppressKibitz = gameMode != IcsObserving ? 2 :
2435                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2436                         } 
2437                         continue;
2438                 } else
2439                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2440                     started = STARTED_CHATTER;
2441                     suppressKibitz = TRUE;
2442                 }
2443             } // [HGM] kibitz: end of patch
2444
2445 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2446
2447             // [HGM] chat: intercept tells by users for which we have an open chat window
2448             channel = -1;
2449             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2450                                            looking_at(buf, &i, "* whispers:") ||
2451                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2452                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2453                 int p;
2454                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2455                 chattingPartner = -1;
2456
2457                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2458                 for(p=0; p<MAX_CHAT; p++) {
2459                     if(channel == atoi(chatPartner[p])) {
2460                     talker[0] = '['; strcat(talker, "]");
2461                     chattingPartner = p; break;
2462                     }
2463                 } else
2464                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2465                 for(p=0; p<MAX_CHAT; p++) {
2466                     if(!strcmp("WHISPER", chatPartner[p])) {
2467                         talker[0] = '['; strcat(talker, "]");
2468                         chattingPartner = p; break;
2469                     }
2470                 }
2471                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2472                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2473                     talker[0] = 0;
2474                     chattingPartner = p; break;
2475                 }
2476                 if(chattingPartner<0) i = oldi; else {
2477                     started = STARTED_COMMENT;
2478                     parse_pos = 0; parse[0] = NULLCHAR;
2479                     savingComment = TRUE;
2480                     suppressKibitz = TRUE;
2481                 }
2482             } // [HGM] chat: end of patch
2483
2484             if (appData.zippyTalk || appData.zippyPlay) {
2485                 /* [DM] Backup address for color zippy lines */
2486                 backup = i;
2487 #if ZIPPY
2488        #ifdef WIN32
2489                if (loggedOn == TRUE)
2490                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2491                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2492        #else
2493                 if (ZippyControl(buf, &i) ||
2494                     ZippyConverse(buf, &i) ||
2495                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2496                       loggedOn = TRUE;
2497                       if (!appData.colorize) continue;
2498                 }
2499        #endif
2500 #endif
2501             } // [DM] 'else { ' deleted
2502                 if (
2503                     /* Regular tells and says */
2504                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2505                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2506                     looking_at(buf, &i, "* says: ") ||
2507                     /* Don't color "message" or "messages" output */
2508                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2509                     looking_at(buf, &i, "*. * at *:*: ") ||
2510                     looking_at(buf, &i, "--* (*:*): ") ||
2511                     /* Message notifications (same color as tells) */
2512                     looking_at(buf, &i, "* has left a message ") ||
2513                     looking_at(buf, &i, "* just sent you a message:\n") ||
2514                     /* Whispers and kibitzes */
2515                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2516                     looking_at(buf, &i, "* kibitzes: ") ||
2517                     /* Channel tells */
2518                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2519
2520                   if (tkind == 1 && strchr(star_match[0], ':')) {
2521                       /* Avoid "tells you:" spoofs in channels */
2522                      tkind = 3;
2523                   }
2524                   if (star_match[0][0] == NULLCHAR ||
2525                       strchr(star_match[0], ' ') ||
2526                       (tkind == 3 && strchr(star_match[1], ' '))) {
2527                     /* Reject bogus matches */
2528                     i = oldi;
2529                   } else {
2530                     if (appData.colorize) {
2531                       if (oldi > next_out) {
2532                         SendToPlayer(&buf[next_out], oldi - next_out);
2533                         next_out = oldi;
2534                       }
2535                       switch (tkind) {
2536                       case 1:
2537                         Colorize(ColorTell, FALSE);
2538                         curColor = ColorTell;
2539                         break;
2540                       case 2:
2541                         Colorize(ColorKibitz, FALSE);
2542                         curColor = ColorKibitz;
2543                         break;
2544                       case 3:
2545                         p = strrchr(star_match[1], '(');
2546                         if (p == NULL) {
2547                           p = star_match[1];
2548                         } else {
2549                           p++;
2550                         }
2551                         if (atoi(p) == 1) {
2552                           Colorize(ColorChannel1, FALSE);
2553                           curColor = ColorChannel1;
2554                         } else {
2555                           Colorize(ColorChannel, FALSE);
2556                           curColor = ColorChannel;
2557                         }
2558                         break;
2559                       case 5:
2560                         curColor = ColorNormal;
2561                         break;
2562                       }
2563                     }
2564                     if (started == STARTED_NONE && appData.autoComment &&
2565                         (gameMode == IcsObserving ||
2566                          gameMode == IcsPlayingWhite ||
2567                          gameMode == IcsPlayingBlack)) {
2568                       parse_pos = i - oldi;
2569                       memcpy(parse, &buf[oldi], parse_pos);
2570                       parse[parse_pos] = NULLCHAR;
2571                       started = STARTED_COMMENT;
2572                       savingComment = TRUE;
2573                     } else {
2574                       started = STARTED_CHATTER;
2575                       savingComment = FALSE;
2576                     }
2577                     loggedOn = TRUE;
2578                     continue;
2579                   }
2580                 }
2581
2582                 if (looking_at(buf, &i, "* s-shouts: ") ||
2583                     looking_at(buf, &i, "* c-shouts: ")) {
2584                     if (appData.colorize) {
2585                         if (oldi > next_out) {
2586                             SendToPlayer(&buf[next_out], oldi - next_out);
2587                             next_out = oldi;
2588                         }
2589                         Colorize(ColorSShout, FALSE);
2590                         curColor = ColorSShout;
2591                     }
2592                     loggedOn = TRUE;
2593                     started = STARTED_CHATTER;
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "--->")) {
2598                     loggedOn = TRUE;
2599                     continue;
2600                 }
2601
2602                 if (looking_at(buf, &i, "* shouts: ") ||
2603                     looking_at(buf, &i, "--> ")) {
2604                     if (appData.colorize) {
2605                         if (oldi > next_out) {
2606                             SendToPlayer(&buf[next_out], oldi - next_out);
2607                             next_out = oldi;
2608                         }
2609                         Colorize(ColorShout, FALSE);
2610                         curColor = ColorShout;
2611                     }
2612                     loggedOn = TRUE;
2613                     started = STARTED_CHATTER;
2614                     continue;
2615                 }
2616
2617                 if (looking_at( buf, &i, "Challenge:")) {
2618                     if (appData.colorize) {
2619                         if (oldi > next_out) {
2620                             SendToPlayer(&buf[next_out], oldi - next_out);
2621                             next_out = oldi;
2622                         }
2623                         Colorize(ColorChallenge, FALSE);
2624                         curColor = ColorChallenge;
2625                     }
2626                     loggedOn = TRUE;
2627                     continue;
2628                 }
2629
2630                 if (looking_at(buf, &i, "* offers you") ||
2631                     looking_at(buf, &i, "* offers to be") ||
2632                     looking_at(buf, &i, "* would like to") ||
2633                     looking_at(buf, &i, "* requests to") ||
2634                     looking_at(buf, &i, "Your opponent offers") ||
2635                     looking_at(buf, &i, "Your opponent requests")) {
2636
2637                     if (appData.colorize) {
2638                         if (oldi > next_out) {
2639                             SendToPlayer(&buf[next_out], oldi - next_out);
2640                             next_out = oldi;
2641                         }
2642                         Colorize(ColorRequest, FALSE);
2643                         curColor = ColorRequest;
2644                     }
2645                     continue;
2646                 }
2647
2648                 if (looking_at(buf, &i, "* (*) seeking")) {
2649                     if (appData.colorize) {
2650                         if (oldi > next_out) {
2651                             SendToPlayer(&buf[next_out], oldi - next_out);
2652                             next_out = oldi;
2653                         }
2654                         Colorize(ColorSeek, FALSE);
2655                         curColor = ColorSeek;
2656                     }
2657                     continue;
2658             }
2659
2660             if (looking_at(buf, &i, "\\   ")) {
2661                 if (prevColor != ColorNormal) {
2662                     if (oldi > next_out) {
2663                         SendToPlayer(&buf[next_out], oldi - next_out);
2664                         next_out = oldi;
2665                     }
2666                     Colorize(prevColor, TRUE);
2667                     curColor = prevColor;
2668                 }
2669                 if (savingComment) {
2670                     parse_pos = i - oldi;
2671                     memcpy(parse, &buf[oldi], parse_pos);
2672                     parse[parse_pos] = NULLCHAR;
2673                     started = STARTED_COMMENT;
2674                 } else {
2675                     started = STARTED_CHATTER;
2676                 }
2677                 continue;
2678             }
2679
2680             if (looking_at(buf, &i, "Black Strength :") ||
2681                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2682                 looking_at(buf, &i, "<10>") ||
2683                 looking_at(buf, &i, "#@#")) {
2684                 /* Wrong board style */
2685                 loggedOn = TRUE;
2686                 SendToICS(ics_prefix);
2687                 SendToICS("set style 12\n");
2688                 SendToICS(ics_prefix);
2689                 SendToICS("refresh\n");
2690                 continue;
2691             }
2692             
2693             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2694                 ICSInitScript();
2695                 have_sent_ICS_logon = 1;
2696                 continue;
2697             }
2698               
2699             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2700                 (looking_at(buf, &i, "\n<12> ") ||
2701                  looking_at(buf, &i, "<12> "))) {
2702                 loggedOn = TRUE;
2703                 if (oldi > next_out) {
2704                     SendToPlayer(&buf[next_out], oldi - next_out);
2705                 }
2706                 next_out = i;
2707                 started = STARTED_BOARD;
2708                 parse_pos = 0;
2709                 continue;
2710             }
2711
2712             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2713                 looking_at(buf, &i, "<b1> ")) {
2714                 if (oldi > next_out) {
2715                     SendToPlayer(&buf[next_out], oldi - next_out);
2716                 }
2717                 next_out = i;
2718                 started = STARTED_HOLDINGS;
2719                 parse_pos = 0;
2720                 continue;
2721             }
2722
2723             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2724                 loggedOn = TRUE;
2725                 /* Header for a move list -- first line */
2726
2727                 switch (ics_getting_history) {
2728                   case H_FALSE:
2729                     switch (gameMode) {
2730                       case IcsIdle:
2731                       case BeginningOfGame:
2732                         /* User typed "moves" or "oldmoves" while we
2733                            were idle.  Pretend we asked for these
2734                            moves and soak them up so user can step
2735                            through them and/or save them.
2736                            */
2737                         Reset(FALSE, TRUE);
2738                         gameMode = IcsObserving;
2739                         ModeHighlight();
2740                         ics_gamenum = -1;
2741                         ics_getting_history = H_GOT_UNREQ_HEADER;
2742                         break;
2743                       case EditGame: /*?*/
2744                       case EditPosition: /*?*/
2745                         /* Should above feature work in these modes too? */
2746                         /* For now it doesn't */
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                       default:
2750                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2751                         break;
2752                     }
2753                     break;
2754                   case H_REQUESTED:
2755                     /* Is this the right one? */
2756                     if (gameInfo.white && gameInfo.black &&
2757                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2758                         strcmp(gameInfo.black, star_match[2]) == 0) {
2759                         /* All is well */
2760                         ics_getting_history = H_GOT_REQ_HEADER;
2761                     }
2762                     break;
2763                   case H_GOT_REQ_HEADER:
2764                   case H_GOT_UNREQ_HEADER:
2765                   case H_GOT_UNWANTED_HEADER:
2766                   case H_GETTING_MOVES:
2767                     /* Should not happen */
2768                     DisplayError(_("Error gathering move list: two headers"), 0);
2769                     ics_getting_history = H_FALSE;
2770                     break;
2771                 }
2772
2773                 /* Save player ratings into gameInfo if needed */
2774                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2775                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2776                     (gameInfo.whiteRating == -1 ||
2777                      gameInfo.blackRating == -1)) {
2778
2779                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2780                     gameInfo.blackRating = string_to_rating(star_match[3]);
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2783                               gameInfo.whiteRating, gameInfo.blackRating);
2784                 }
2785                 continue;
2786             }
2787
2788             if (looking_at(buf, &i,
2789               "* * match, initial time: * minute*, increment: * second")) {
2790                 /* Header for a move list -- second line */
2791                 /* Initial board will follow if this is a wild game */
2792                 if (gameInfo.event != NULL) free(gameInfo.event);
2793                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2794                 gameInfo.event = StrSave(str);
2795                 /* [HGM] we switched variant. Translate boards if needed. */
2796                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2797                 continue;
2798             }
2799
2800             if (looking_at(buf, &i, "Move  ")) {
2801                 /* Beginning of a move list */
2802                 switch (ics_getting_history) {
2803                   case H_FALSE:
2804                     /* Normally should not happen */
2805                     /* Maybe user hit reset while we were parsing */
2806                     break;
2807                   case H_REQUESTED:
2808                     /* Happens if we are ignoring a move list that is not
2809                      * the one we just requested.  Common if the user
2810                      * tries to observe two games without turning off
2811                      * getMoveList */
2812                     break;
2813                   case H_GETTING_MOVES:
2814                     /* Should not happen */
2815                     DisplayError(_("Error gathering move list: nested"), 0);
2816                     ics_getting_history = H_FALSE;
2817                     break;
2818                   case H_GOT_REQ_HEADER:
2819                     ics_getting_history = H_GETTING_MOVES;
2820                     started = STARTED_MOVES;
2821                     parse_pos = 0;
2822                     if (oldi > next_out) {
2823                         SendToPlayer(&buf[next_out], oldi - next_out);
2824                     }
2825                     break;
2826                   case H_GOT_UNREQ_HEADER:
2827                     ics_getting_history = H_GETTING_MOVES;
2828                     started = STARTED_MOVES_NOHIDE;
2829                     parse_pos = 0;
2830                     break;
2831                   case H_GOT_UNWANTED_HEADER:
2832                     ics_getting_history = H_FALSE;
2833                     break;
2834                 }
2835                 continue;
2836             }                           
2837             
2838             if (looking_at(buf, &i, "% ") ||
2839                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2840                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2841                 savingComment = FALSE;
2842                 switch (started) {
2843                   case STARTED_MOVES:
2844                   case STARTED_MOVES_NOHIDE:
2845                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2846                     parse[parse_pos + i - oldi] = NULLCHAR;
2847                     ParseGameHistory(parse);
2848 #if ZIPPY
2849                     if (appData.zippyPlay && first.initDone) {
2850                         FeedMovesToProgram(&first, forwardMostMove);
2851                         if (gameMode == IcsPlayingWhite) {
2852                             if (WhiteOnMove(forwardMostMove)) {
2853                                 if (first.sendTime) {
2854                                   if (first.useColors) {
2855                                     SendToProgram("black\n", &first); 
2856                                   }
2857                                   SendTimeRemaining(&first, TRUE);
2858                                 }
2859                                 if (first.useColors) {
2860                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2861                                 }
2862                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2863                                 first.maybeThinking = TRUE;
2864                             } else {
2865                                 if (first.usePlayother) {
2866                                   if (first.sendTime) {
2867                                     SendTimeRemaining(&first, TRUE);
2868                                   }
2869                                   SendToProgram("playother\n", &first);
2870                                   firstMove = FALSE;
2871                                 } else {
2872                                   firstMove = TRUE;
2873                                 }
2874                             }
2875                         } else if (gameMode == IcsPlayingBlack) {
2876                             if (!WhiteOnMove(forwardMostMove)) {
2877                                 if (first.sendTime) {
2878                                   if (first.useColors) {
2879                                     SendToProgram("white\n", &first);
2880                                   }
2881                                   SendTimeRemaining(&first, FALSE);
2882                                 }
2883                                 if (first.useColors) {
2884                                   SendToProgram("black\n", &first);
2885                                 }
2886                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2887                                 first.maybeThinking = TRUE;
2888                             } else {
2889                                 if (first.usePlayother) {
2890                                   if (first.sendTime) {
2891                                     SendTimeRemaining(&first, FALSE);
2892                                   }
2893                                   SendToProgram("playother\n", &first);
2894                                   firstMove = FALSE;
2895                                 } else {
2896                                   firstMove = TRUE;
2897                                 }
2898                             }
2899                         }                       
2900                     }
2901 #endif
2902                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2903                         /* Moves came from oldmoves or moves command
2904                            while we weren't doing anything else.
2905                            */
2906                         currentMove = forwardMostMove;
2907                         ClearHighlights();/*!!could figure this out*/
2908                         flipView = appData.flipView;
2909                         DrawPosition(TRUE, boards[currentMove]);
2910                         DisplayBothClocks();
2911                         sprintf(str, "%s vs. %s",
2912                                 gameInfo.white, gameInfo.black);
2913                         DisplayTitle(str);
2914                         gameMode = IcsIdle;
2915                     } else {
2916                         /* Moves were history of an active game */
2917                         if (gameInfo.resultDetails != NULL) {
2918                             free(gameInfo.resultDetails);
2919                             gameInfo.resultDetails = NULL;
2920                         }
2921                     }
2922                     HistorySet(parseList, backwardMostMove,
2923                                forwardMostMove, currentMove-1);
2924                     DisplayMove(currentMove - 1);
2925                     if (started == STARTED_MOVES) next_out = i;
2926                     started = STARTED_NONE;
2927                     ics_getting_history = H_FALSE;
2928                     break;
2929
2930                   case STARTED_OBSERVE:
2931                     started = STARTED_NONE;
2932                     SendToICS(ics_prefix);
2933                     SendToICS("refresh\n");
2934                     break;
2935
2936                   default:
2937                     break;
2938                 }
2939                 if(bookHit) { // [HGM] book: simulate book reply
2940                     static char bookMove[MSG_SIZ]; // a bit generous?
2941
2942                     programStats.nodes = programStats.depth = programStats.time = 
2943                     programStats.score = programStats.got_only_move = 0;
2944                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2945
2946                     strcpy(bookMove, "move ");
2947                     strcat(bookMove, bookHit);
2948                     HandleMachineMove(bookMove, &first);
2949                 }
2950                 continue;
2951             }
2952             
2953             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2954                  started == STARTED_HOLDINGS ||
2955                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2956                 /* Accumulate characters in move list or board */
2957                 parse[parse_pos++] = buf[i];
2958             }
2959             
2960             /* Start of game messages.  Mostly we detect start of game
2961                when the first board image arrives.  On some versions
2962                of the ICS, though, we need to do a "refresh" after starting
2963                to observe in order to get the current board right away. */
2964             if (looking_at(buf, &i, "Adding game * to observation list")) {
2965                 started = STARTED_OBSERVE;
2966                 continue;
2967             }
2968
2969             /* Handle auto-observe */
2970             if (appData.autoObserve &&
2971                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2972                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2973                 char *player;
2974                 /* Choose the player that was highlighted, if any. */
2975                 if (star_match[0][0] == '\033' ||
2976                     star_match[1][0] != '\033') {
2977                     player = star_match[0];
2978                 } else {
2979                     player = star_match[2];
2980                 }
2981                 sprintf(str, "%sobserve %s\n",
2982                         ics_prefix, StripHighlightAndTitle(player));
2983                 SendToICS(str);
2984
2985                 /* Save ratings from notify string */
2986                 strcpy(player1Name, star_match[0]);
2987                 player1Rating = string_to_rating(star_match[1]);
2988                 strcpy(player2Name, star_match[2]);
2989                 player2Rating = string_to_rating(star_match[3]);
2990
2991                 if (appData.debugMode)
2992                   fprintf(debugFP, 
2993                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2994                           player1Name, player1Rating,
2995                           player2Name, player2Rating);
2996
2997                 continue;
2998             }
2999
3000             /* Deal with automatic examine mode after a game,
3001                and with IcsObserving -> IcsExamining transition */
3002             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3003                 looking_at(buf, &i, "has made you an examiner of game *")) {
3004
3005                 int gamenum = atoi(star_match[0]);
3006                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3007                     gamenum == ics_gamenum) {
3008                     /* We were already playing or observing this game;
3009                        no need to refetch history */
3010                     gameMode = IcsExamining;
3011                     if (pausing) {
3012                         pauseExamForwardMostMove = forwardMostMove;
3013                     } else if (currentMove < forwardMostMove) {
3014                         ForwardInner(forwardMostMove);
3015                     }
3016                 } else {
3017                     /* I don't think this case really can happen */
3018                     SendToICS(ics_prefix);
3019                     SendToICS("refresh\n");
3020                 }
3021                 continue;
3022             }    
3023             
3024             /* Error messages */
3025 //          if (ics_user_moved) {
3026             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3027                 if (looking_at(buf, &i, "Illegal move") ||
3028                     looking_at(buf, &i, "Not a legal move") ||
3029                     looking_at(buf, &i, "Your king is in check") ||
3030                     looking_at(buf, &i, "It isn't your turn") ||
3031                     looking_at(buf, &i, "It is not your move")) {
3032                     /* Illegal move */
3033                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3034                         currentMove = --forwardMostMove;
3035                         DisplayMove(currentMove - 1); /* before DMError */
3036                         DrawPosition(FALSE, boards[currentMove]);
3037                         SwitchClocks();
3038                         DisplayBothClocks();
3039                     }
3040                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3041                     ics_user_moved = 0;
3042                     continue;
3043                 }
3044             }
3045
3046             if (looking_at(buf, &i, "still have time") ||
3047                 looking_at(buf, &i, "not out of time") ||
3048                 looking_at(buf, &i, "either player is out of time") ||
3049                 looking_at(buf, &i, "has timeseal; checking")) {
3050                 /* We must have called his flag a little too soon */
3051                 whiteFlag = blackFlag = FALSE;
3052                 continue;
3053             }
3054
3055             if (looking_at(buf, &i, "added * seconds to") ||
3056                 looking_at(buf, &i, "seconds were added to")) {
3057                 /* Update the clocks */
3058                 SendToICS(ics_prefix);
3059                 SendToICS("refresh\n");
3060                 continue;
3061             }
3062
3063             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3064                 ics_clock_paused = TRUE;
3065                 StopClocks();
3066                 continue;
3067             }
3068
3069             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3070                 ics_clock_paused = FALSE;
3071                 StartClocks();
3072                 continue;
3073             }
3074
3075             /* Grab player ratings from the Creating: message.
3076                Note we have to check for the special case when
3077                the ICS inserts things like [white] or [black]. */
3078             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3079                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3080                 /* star_matches:
3081                    0    player 1 name (not necessarily white)
3082                    1    player 1 rating
3083                    2    empty, white, or black (IGNORED)
3084                    3    player 2 name (not necessarily black)
3085                    4    player 2 rating
3086                    
3087                    The names/ratings are sorted out when the game
3088                    actually starts (below).
3089                 */
3090                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3091                 player1Rating = string_to_rating(star_match[1]);
3092                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3093                 player2Rating = string_to_rating(star_match[4]);
3094
3095                 if (appData.debugMode)
3096                   fprintf(debugFP, 
3097                           "Ratings from 'Creating:' %s %d, %s %d\n",
3098                           player1Name, player1Rating,
3099                           player2Name, player2Rating);
3100
3101                 continue;
3102             }
3103             
3104             /* Improved generic start/end-of-game messages */
3105             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3106                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3107                 /* If tkind == 0: */
3108                 /* star_match[0] is the game number */
3109                 /*           [1] is the white player's name */
3110                 /*           [2] is the black player's name */
3111                 /* For end-of-game: */
3112                 /*           [3] is the reason for the game end */
3113                 /*           [4] is a PGN end game-token, preceded by " " */
3114                 /* For start-of-game: */
3115                 /*           [3] begins with "Creating" or "Continuing" */
3116                 /*           [4] is " *" or empty (don't care). */
3117                 int gamenum = atoi(star_match[0]);
3118                 char *whitename, *blackname, *why, *endtoken;
3119                 ChessMove endtype = (ChessMove) 0;
3120
3121                 if (tkind == 0) {
3122                   whitename = star_match[1];
3123                   blackname = star_match[2];
3124                   why = star_match[3];
3125                   endtoken = star_match[4];
3126                 } else {
3127                   whitename = star_match[1];
3128                   blackname = star_match[3];
3129                   why = star_match[5];
3130                   endtoken = star_match[6];
3131                 }
3132
3133                 /* Game start messages */
3134                 if (strncmp(why, "Creating ", 9) == 0 ||
3135                     strncmp(why, "Continuing ", 11) == 0) {
3136                     gs_gamenum = gamenum;
3137                     strcpy(gs_kind, strchr(why, ' ') + 1);
3138 #if ZIPPY
3139                     if (appData.zippyPlay) {
3140                         ZippyGameStart(whitename, blackname);
3141                     }
3142 #endif /*ZIPPY*/
3143                     continue;
3144                 }
3145
3146                 /* Game end messages */
3147                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3148                     ics_gamenum != gamenum) {
3149                     continue;
3150                 }
3151                 while (endtoken[0] == ' ') endtoken++;
3152                 switch (endtoken[0]) {
3153                   case '*':
3154                   default:
3155                     endtype = GameUnfinished;
3156                     break;
3157                   case '0':
3158                     endtype = BlackWins;
3159                     break;
3160                   case '1':
3161                     if (endtoken[1] == '/')
3162                       endtype = GameIsDrawn;
3163                     else
3164                       endtype = WhiteWins;
3165                     break;
3166                 }
3167                 GameEnds(endtype, why, GE_ICS);
3168 #if ZIPPY
3169                 if (appData.zippyPlay && first.initDone) {
3170                     ZippyGameEnd(endtype, why);
3171                     if (first.pr == NULL) {
3172                       /* Start the next process early so that we'll
3173                          be ready for the next challenge */
3174                       StartChessProgram(&first);
3175                     }
3176                     /* Send "new" early, in case this command takes
3177                        a long time to finish, so that we'll be ready
3178                        for the next challenge. */
3179                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3180                     Reset(TRUE, TRUE);
3181                 }
3182 #endif /*ZIPPY*/
3183                 continue;
3184             }
3185
3186             if (looking_at(buf, &i, "Removing game * from observation") ||
3187                 looking_at(buf, &i, "no longer observing game *") ||
3188                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3189                 if (gameMode == IcsObserving &&
3190                     atoi(star_match[0]) == ics_gamenum)
3191                   {
3192                       /* icsEngineAnalyze */
3193                       if (appData.icsEngineAnalyze) {
3194                             ExitAnalyzeMode();
3195                             ModeHighlight();
3196                       }
3197                       StopClocks();
3198                       gameMode = IcsIdle;
3199                       ics_gamenum = -1;
3200                       ics_user_moved = FALSE;
3201                   }
3202                 continue;
3203             }
3204
3205             if (looking_at(buf, &i, "no longer examining game *")) {
3206                 if (gameMode == IcsExamining &&
3207                     atoi(star_match[0]) == ics_gamenum)
3208                   {
3209                       gameMode = IcsIdle;
3210                       ics_gamenum = -1;
3211                       ics_user_moved = FALSE;
3212                   }
3213                 continue;
3214             }
3215
3216             /* Advance leftover_start past any newlines we find,
3217                so only partial lines can get reparsed */
3218             if (looking_at(buf, &i, "\n")) {
3219                 prevColor = curColor;
3220                 if (curColor != ColorNormal) {
3221                     if (oldi > next_out) {
3222                         SendToPlayer(&buf[next_out], oldi - next_out);
3223                         next_out = oldi;
3224                     }
3225                     Colorize(ColorNormal, FALSE);
3226                     curColor = ColorNormal;
3227                 }
3228                 if (started == STARTED_BOARD) {
3229                     started = STARTED_NONE;
3230                     parse[parse_pos] = NULLCHAR;
3231                     ParseBoard12(parse);
3232                     ics_user_moved = 0;
3233
3234                     /* Send premove here */
3235                     if (appData.premove) {
3236                       char str[MSG_SIZ];
3237                       if (currentMove == 0 &&
3238                           gameMode == IcsPlayingWhite &&
3239                           appData.premoveWhite) {
3240                         sprintf(str, "%s\n", appData.premoveWhiteText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (currentMove == 1 &&
3245                                  gameMode == IcsPlayingBlack &&
3246                                  appData.premoveBlack) {
3247                         sprintf(str, "%s\n", appData.premoveBlackText);
3248                         if (appData.debugMode)
3249                           fprintf(debugFP, "Sending premove:\n");
3250                         SendToICS(str);
3251                       } else if (gotPremove) {
3252                         gotPremove = 0;
3253                         ClearPremoveHighlights();
3254                         if (appData.debugMode)
3255                           fprintf(debugFP, "Sending premove:\n");
3256                           UserMoveEvent(premoveFromX, premoveFromY, 
3257                                         premoveToX, premoveToY, 
3258                                         premovePromoChar);
3259                       }
3260                     }
3261
3262                     /* Usually suppress following prompt */
3263                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3264                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3265                         if (looking_at(buf, &i, "*% ")) {
3266                             savingComment = FALSE;
3267                         }
3268                     }
3269                     next_out = i;
3270                 } else if (started == STARTED_HOLDINGS) {
3271                     int gamenum;
3272                     char new_piece[MSG_SIZ];
3273                     started = STARTED_NONE;
3274                     parse[parse_pos] = NULLCHAR;
3275                     if (appData.debugMode)
3276                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3277                                                         parse, currentMove);
3278                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3279                         gamenum == ics_gamenum) {
3280                         if (gameInfo.variant == VariantNormal) {
3281                           /* [HGM] We seem to switch variant during a game!
3282                            * Presumably no holdings were displayed, so we have
3283                            * to move the position two files to the right to
3284                            * create room for them!
3285                            */
3286                           VariantClass newVariant;
3287                           switch(gameInfo.boardWidth) { // base guess on board width
3288                                 case 9:  newVariant = VariantShogi; break;
3289                                 case 10: newVariant = VariantGreat; break;
3290                                 default: newVariant = VariantCrazyhouse; break;
3291                           }
3292                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3293                           /* Get a move list just to see the header, which
3294                              will tell us whether this is really bug or zh */
3295                           if (ics_getting_history == H_FALSE) {
3296                             ics_getting_history = H_REQUESTED;
3297                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3298                             SendToICS(str);
3299                           }
3300                         }
3301                         new_piece[0] = NULLCHAR;
3302                         sscanf(parse, "game %d white [%s black [%s <- %s",
3303                                &gamenum, white_holding, black_holding,
3304                                new_piece);
3305                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3306                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3307                         /* [HGM] copy holdings to board holdings area */
3308                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3309                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3310                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3311 #if ZIPPY
3312                         if (appData.zippyPlay && first.initDone) {
3313                             ZippyHoldings(white_holding, black_holding,
3314                                           new_piece);
3315                         }
3316 #endif /*ZIPPY*/
3317                         if (tinyLayout || smallLayout) {
3318                             char wh[16], bh[16];
3319                             PackHolding(wh, white_holding);
3320                             PackHolding(bh, black_holding);
3321                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3322                                     gameInfo.white, gameInfo.black);
3323                         } else {
3324                             sprintf(str, "%s [%s] vs. %s [%s]",
3325                                     gameInfo.white, white_holding,
3326                                     gameInfo.black, black_holding);
3327                         }
3328
3329                         DrawPosition(FALSE, boards[currentMove]);
3330                         DisplayTitle(str);
3331                     }
3332                     /* Suppress following prompt */
3333                     if (looking_at(buf, &i, "*% ")) {
3334                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3335                         savingComment = FALSE;
3336                     }
3337                     next_out = i;
3338                 }
3339                 continue;
3340             }
3341
3342             i++;                /* skip unparsed character and loop back */
3343         }
3344         
3345         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3346             started != STARTED_HOLDINGS && i > next_out) {
3347             SendToPlayer(&buf[next_out], i - next_out);
3348             next_out = i;
3349         }
3350         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3351         
3352         leftover_len = buf_len - leftover_start;
3353         /* if buffer ends with something we couldn't parse,
3354            reparse it after appending the next read */
3355         
3356     } else if (count == 0) {
3357         RemoveInputSource(isr);
3358         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3359     } else {
3360         DisplayFatalError(_("Error reading from ICS"), error, 1);
3361     }
3362 }
3363
3364
3365 /* Board style 12 looks like this:
3366    
3367    <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
3368    
3369  * The "<12> " is stripped before it gets to this routine.  The two
3370  * trailing 0's (flip state and clock ticking) are later addition, and
3371  * some chess servers may not have them, or may have only the first.
3372  * Additional trailing fields may be added in the future.  
3373  */
3374
3375 #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"
3376
3377 #define RELATION_OBSERVING_PLAYED    0
3378 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3379 #define RELATION_PLAYING_MYMOVE      1
3380 #define RELATION_PLAYING_NOTMYMOVE  -1
3381 #define RELATION_EXAMINING           2
3382 #define RELATION_ISOLATED_BOARD     -3
3383 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3384
3385 void
3386 ParseBoard12(string)
3387      char *string;
3388
3389     GameMode newGameMode;
3390     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3391     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3392     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3393     char to_play, board_chars[200];
3394     char move_str[500], str[500], elapsed_time[500];
3395     char black[32], white[32];
3396     Board board;
3397     int prevMove = currentMove;
3398     int ticking = 2;
3399     ChessMove moveType;
3400     int fromX, fromY, toX, toY;
3401     char promoChar;
3402     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3403     char *bookHit = NULL; // [HGM] book
3404     Boolean weird = FALSE, reqFlag = FALSE;
3405
3406     fromX = fromY = toX = toY = -1;
3407     
3408     newGame = FALSE;
3409
3410     if (appData.debugMode)
3411       fprintf(debugFP, _("Parsing board: %s\n"), string);
3412
3413     move_str[0] = NULLCHAR;
3414     elapsed_time[0] = NULLCHAR;
3415     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3416         int  i = 0, j;
3417         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3418             if(string[i] == ' ') { ranks++; files = 0; }
3419             else files++;
3420             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3421             i++;
3422         }
3423         for(j = 0; j <i; j++) board_chars[j] = string[j];
3424         board_chars[i] = '\0';
3425         string += i + 1;
3426     }
3427     n = sscanf(string, PATTERN, &to_play, &double_push,
3428                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3429                &gamenum, white, black, &relation, &basetime, &increment,
3430                &white_stren, &black_stren, &white_time, &black_time,
3431                &moveNum, str, elapsed_time, move_str, &ics_flip,
3432                &ticking);
3433
3434     if (n < 21) {
3435         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3436         DisplayError(str, 0);
3437         return;
3438     }
3439
3440     /* Convert the move number to internal form */
3441     moveNum = (moveNum - 1) * 2;
3442     if (to_play == 'B') moveNum++;
3443     if (moveNum >= MAX_MOVES) {
3444       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3445                         0, 1);
3446       return;
3447     }
3448     
3449     switch (relation) {
3450       case RELATION_OBSERVING_PLAYED:
3451       case RELATION_OBSERVING_STATIC:
3452         if (gamenum == -1) {
3453             /* Old ICC buglet */
3454             relation = RELATION_OBSERVING_STATIC;
3455         }
3456         newGameMode = IcsObserving;
3457         break;
3458       case RELATION_PLAYING_MYMOVE:
3459       case RELATION_PLAYING_NOTMYMOVE:
3460         newGameMode =
3461           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3462             IcsPlayingWhite : IcsPlayingBlack;
3463         break;
3464       case RELATION_EXAMINING:
3465         newGameMode = IcsExamining;
3466         break;
3467       case RELATION_ISOLATED_BOARD:
3468       default:
3469         /* Just display this board.  If user was doing something else,
3470            we will forget about it until the next board comes. */ 
3471         newGameMode = IcsIdle;
3472         break;
3473       case RELATION_STARTING_POSITION:
3474         newGameMode = gameMode;
3475         break;
3476     }
3477     
3478     /* Modify behavior for initial board display on move listing
3479        of wild games.
3480        */
3481     switch (ics_getting_history) {
3482       case H_FALSE:
3483       case H_REQUESTED:
3484         break;
3485       case H_GOT_REQ_HEADER:
3486       case H_GOT_UNREQ_HEADER:
3487         /* This is the initial position of the current game */
3488         gamenum = ics_gamenum;
3489         moveNum = 0;            /* old ICS bug workaround */
3490         if (to_play == 'B') {
3491           startedFromSetupPosition = TRUE;
3492           blackPlaysFirst = TRUE;
3493           moveNum = 1;
3494           if (forwardMostMove == 0) forwardMostMove = 1;
3495           if (backwardMostMove == 0) backwardMostMove = 1;
3496           if (currentMove == 0) currentMove = 1;
3497         }
3498         newGameMode = gameMode;
3499         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3500         break;
3501       case H_GOT_UNWANTED_HEADER:
3502         /* This is an initial board that we don't want */
3503         return;
3504       case H_GETTING_MOVES:
3505         /* Should not happen */
3506         DisplayError(_("Error gathering move list: extra board"), 0);
3507         ics_getting_history = H_FALSE;
3508         return;
3509     }
3510
3511    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3512                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3513      /* [HGM] We seem to have switched variant unexpectedly
3514       * Try to guess new variant from board size
3515       */
3516           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3517           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3518           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3519           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3520           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3521           if(!weird) newVariant = VariantNormal;
3522           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3523           /* Get a move list just to see the header, which
3524              will tell us whether this is really bug or zh */
3525           if (ics_getting_history == H_FALSE) {
3526             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3527             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3528             SendToICS(str);
3529           }
3530     }
3531     
3532     /* Take action if this is the first board of a new game, or of a
3533        different game than is currently being displayed.  */
3534     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3535         relation == RELATION_ISOLATED_BOARD) {
3536         
3537         /* Forget the old game and get the history (if any) of the new one */
3538         if (gameMode != BeginningOfGame) {
3539           Reset(TRUE, TRUE);
3540         }
3541         newGame = TRUE;
3542         if (appData.autoRaiseBoard) BoardToTop();
3543         prevMove = -3;
3544         if (gamenum == -1) {
3545             newGameMode = IcsIdle;
3546         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3547                    appData.getMoveList && !reqFlag) {
3548             /* Need to get game history */
3549             ics_getting_history = H_REQUESTED;
3550             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3551             SendToICS(str);
3552         }
3553         
3554         /* Initially flip the board to have black on the bottom if playing
3555            black or if the ICS flip flag is set, but let the user change
3556            it with the Flip View button. */
3557         flipView = appData.autoFlipView ? 
3558           (newGameMode == IcsPlayingBlack) || ics_flip :
3559           appData.flipView;
3560         
3561         /* Done with values from previous mode; copy in new ones */
3562         gameMode = newGameMode;
3563         ModeHighlight();
3564         ics_gamenum = gamenum;
3565         if (gamenum == gs_gamenum) {
3566             int klen = strlen(gs_kind);
3567             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3568             sprintf(str, "ICS %s", gs_kind);
3569             gameInfo.event = StrSave(str);
3570         } else {
3571             gameInfo.event = StrSave("ICS game");
3572         }
3573         gameInfo.site = StrSave(appData.icsHost);
3574         gameInfo.date = PGNDate();
3575         gameInfo.round = StrSave("-");
3576         gameInfo.white = StrSave(white);
3577         gameInfo.black = StrSave(black);
3578         timeControl = basetime * 60 * 1000;
3579         timeControl_2 = 0;
3580         timeIncrement = increment * 1000;
3581         movesPerSession = 0;
3582         gameInfo.timeControl = TimeControlTagValue();
3583         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3584   if (appData.debugMode) {
3585     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3586     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3587     setbuf(debugFP, NULL);
3588   }
3589
3590         gameInfo.outOfBook = NULL;
3591         
3592         /* Do we have the ratings? */
3593         if (strcmp(player1Name, white) == 0 &&
3594             strcmp(player2Name, black) == 0) {
3595             if (appData.debugMode)
3596               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3597                       player1Rating, player2Rating);
3598             gameInfo.whiteRating = player1Rating;
3599             gameInfo.blackRating = player2Rating;
3600         } else if (strcmp(player2Name, white) == 0 &&
3601                    strcmp(player1Name, black) == 0) {
3602             if (appData.debugMode)
3603               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3604                       player2Rating, player1Rating);
3605             gameInfo.whiteRating = player2Rating;
3606             gameInfo.blackRating = player1Rating;
3607         }
3608         player1Name[0] = player2Name[0] = NULLCHAR;
3609
3610         /* Silence shouts if requested */
3611         if (appData.quietPlay &&
3612             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3613             SendToICS(ics_prefix);
3614             SendToICS("set shout 0\n");
3615         }
3616     }
3617     
3618     /* Deal with midgame name changes */
3619     if (!newGame) {
3620         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3621             if (gameInfo.white) free(gameInfo.white);
3622             gameInfo.white = StrSave(white);
3623         }
3624         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3625             if (gameInfo.black) free(gameInfo.black);
3626             gameInfo.black = StrSave(black);
3627         }
3628     }
3629     
3630     /* Throw away game result if anything actually changes in examine mode */
3631     if (gameMode == IcsExamining && !newGame) {
3632         gameInfo.result = GameUnfinished;
3633         if (gameInfo.resultDetails != NULL) {
3634             free(gameInfo.resultDetails);
3635             gameInfo.resultDetails = NULL;
3636         }
3637     }
3638     
3639     /* In pausing && IcsExamining mode, we ignore boards coming
3640        in if they are in a different variation than we are. */
3641     if (pauseExamInvalid) return;
3642     if (pausing && gameMode == IcsExamining) {
3643         if (moveNum <= pauseExamForwardMostMove) {
3644             pauseExamInvalid = TRUE;
3645             forwardMostMove = pauseExamForwardMostMove;
3646             return;
3647         }
3648     }
3649     
3650   if (appData.debugMode) {
3651     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3652   }
3653     /* Parse the board */
3654     for (k = 0; k < ranks; k++) {
3655       for (j = 0; j < files; j++)
3656         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3657       if(gameInfo.holdingsWidth > 1) {
3658            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3659            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3660       }
3661     }
3662     CopyBoard(boards[moveNum], board);
3663     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3664     if (moveNum == 0) {
3665         startedFromSetupPosition =
3666           !CompareBoards(board, initialPosition);
3667         if(startedFromSetupPosition)
3668             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3669     }
3670
3671     /* [HGM] Set castling rights. Take the outermost Rooks,
3672        to make it also work for FRC opening positions. Note that board12
3673        is really defective for later FRC positions, as it has no way to
3674        indicate which Rook can castle if they are on the same side of King.
3675        For the initial position we grant rights to the outermost Rooks,
3676        and remember thos rights, and we then copy them on positions
3677        later in an FRC game. This means WB might not recognize castlings with
3678        Rooks that have moved back to their original position as illegal,
3679        but in ICS mode that is not its job anyway.
3680     */
3681     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3682     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3683
3684         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685             if(board[0][i] == WhiteRook) j = i;
3686         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688             if(board[0][i] == WhiteRook) j = i;
3689         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3690         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3693         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3694             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3695         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3696
3697         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3700         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3701             if(board[BOARD_HEIGHT-1][k] == bKing)
3702                 initialRights[5] = castlingRights[moveNum][5] = k;
3703     } else { int r;
3704         r = castlingRights[moveNum][0] = initialRights[0];
3705         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3706         r = castlingRights[moveNum][1] = initialRights[1];
3707         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3708         r = castlingRights[moveNum][3] = initialRights[3];
3709         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3710         r = castlingRights[moveNum][4] = initialRights[4];
3711         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3712         /* wildcastle kludge: always assume King has rights */
3713         r = castlingRights[moveNum][2] = initialRights[2];
3714         r = castlingRights[moveNum][5] = initialRights[5];
3715     }
3716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3717     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3718
3719     
3720     if (ics_getting_history == H_GOT_REQ_HEADER ||
3721         ics_getting_history == H_GOT_UNREQ_HEADER) {
3722         /* This was an initial position from a move list, not
3723            the current position */
3724         return;
3725     }
3726     
3727     /* Update currentMove and known move number limits */
3728     newMove = newGame || moveNum > forwardMostMove;
3729
3730     if (newGame) {
3731         forwardMostMove = backwardMostMove = currentMove = moveNum;
3732         if (gameMode == IcsExamining && moveNum == 0) {
3733           /* Workaround for ICS limitation: we are not told the wild
3734              type when starting to examine a game.  But if we ask for
3735              the move list, the move list header will tell us */
3736             ics_getting_history = H_REQUESTED;
3737             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3738             SendToICS(str);
3739         }
3740     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3741                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3742 #if ZIPPY
3743         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3744         /* [HGM] applied this also to an engine that is silently watching        */
3745         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3746             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3747             gameInfo.variant == currentlyInitializedVariant) {
3748           takeback = forwardMostMove - moveNum;
3749           for (i = 0; i < takeback; i++) {
3750             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3751             SendToProgram("undo\n", &first);
3752           }
3753         }
3754 #endif
3755
3756         forwardMostMove = moveNum;
3757         if (!pausing || currentMove > forwardMostMove)
3758           currentMove = forwardMostMove;
3759     } else {
3760         /* New part of history that is not contiguous with old part */ 
3761         if (pausing && gameMode == IcsExamining) {
3762             pauseExamInvalid = TRUE;
3763             forwardMostMove = pauseExamForwardMostMove;
3764             return;
3765         }
3766         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3767 #if ZIPPY
3768             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3769                 // [HGM] when we will receive the move list we now request, it will be
3770                 // fed to the engine from the first move on. So if the engine is not
3771                 // in the initial position now, bring it there.
3772                 InitChessProgram(&first, 0);
3773             }
3774 #endif
3775             ics_getting_history = H_REQUESTED;
3776             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3777             SendToICS(str);
3778         }
3779         forwardMostMove = backwardMostMove = currentMove = moveNum;
3780     }
3781     
3782     /* Update the clocks */
3783     if (strchr(elapsed_time, '.')) {
3784       /* Time is in ms */
3785       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3786       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3787     } else {
3788       /* Time is in seconds */
3789       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3790       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3791     }
3792       
3793
3794 #if ZIPPY
3795     if (appData.zippyPlay && newGame &&
3796         gameMode != IcsObserving && gameMode != IcsIdle &&
3797         gameMode != IcsExamining)
3798       ZippyFirstBoard(moveNum, basetime, increment);
3799 #endif
3800     
3801     /* Put the move on the move list, first converting
3802        to canonical algebraic form. */
3803     if (moveNum > 0) {
3804   if (appData.debugMode) {
3805     if (appData.debugMode) { int f = forwardMostMove;
3806         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3807                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3808     }
3809     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3810     fprintf(debugFP, "moveNum = %d\n", moveNum);
3811     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3812     setbuf(debugFP, NULL);
3813   }
3814         if (moveNum <= backwardMostMove) {
3815             /* We don't know what the board looked like before
3816                this move.  Punt. */
3817             strcpy(parseList[moveNum - 1], move_str);
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             moveList[moveNum - 1][0] = NULLCHAR;
3821         } else if (strcmp(move_str, "none") == 0) {
3822             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3823             /* Again, we don't know what the board looked like;
3824                this is really the start of the game. */
3825             parseList[moveNum - 1][0] = NULLCHAR;
3826             moveList[moveNum - 1][0] = NULLCHAR;
3827             backwardMostMove = moveNum;
3828             startedFromSetupPosition = TRUE;
3829             fromX = fromY = toX = toY = -1;
3830         } else {
3831           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3832           //                 So we parse the long-algebraic move string in stead of the SAN move
3833           int valid; char buf[MSG_SIZ], *prom;
3834
3835           // str looks something like "Q/a1-a2"; kill the slash
3836           if(str[1] == '/') 
3837                 sprintf(buf, "%c%s", str[0], str+2);
3838           else  strcpy(buf, str); // might be castling
3839           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3840                 strcat(buf, prom); // long move lacks promo specification!
3841           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3842                 if(appData.debugMode) 
3843                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3844                 strcpy(move_str, buf);
3845           }
3846           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar)
3848                || ParseOneMove(buf, moveNum - 1, &moveType,
3849                                 &fromX, &fromY, &toX, &toY, &promoChar);
3850           // end of long SAN patch
3851           if (valid) {
3852             (void) CoordsToAlgebraic(boards[moveNum - 1],
3853                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3854                                      fromY, fromX, toY, toX, promoChar,
3855                                      parseList[moveNum-1]);
3856             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3857                              castlingRights[moveNum]) ) {
3858               case MT_NONE:
3859               case MT_STALEMATE:
3860               default:
3861                 break;
3862               case MT_CHECK:
3863                 if(gameInfo.variant != VariantShogi)
3864                     strcat(parseList[moveNum - 1], "+");
3865                 break;
3866               case MT_CHECKMATE:
3867               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3868                 strcat(parseList[moveNum - 1], "#");
3869                 break;
3870             }
3871             strcat(parseList[moveNum - 1], " ");
3872             strcat(parseList[moveNum - 1], elapsed_time);
3873             /* currentMoveString is set as a side-effect of ParseOneMove */
3874             strcpy(moveList[moveNum - 1], currentMoveString);
3875             strcat(moveList[moveNum - 1], "\n");
3876           } else {
3877             /* Move from ICS was illegal!?  Punt. */
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3880     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3881   }
3882             strcpy(parseList[moveNum - 1], move_str);
3883             strcat(parseList[moveNum - 1], " ");
3884             strcat(parseList[moveNum - 1], elapsed_time);
3885             moveList[moveNum - 1][0] = NULLCHAR;
3886             fromX = fromY = toX = toY = -1;
3887           }
3888         }
3889   if (appData.debugMode) {
3890     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3891     setbuf(debugFP, NULL);
3892   }
3893
3894 #if ZIPPY
3895         /* Send move to chess program (BEFORE animating it). */
3896         if (appData.zippyPlay && !newGame && newMove && 
3897            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3898
3899             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3900                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3901                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3902                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3903                             move_str);
3904                     DisplayError(str, 0);
3905                 } else {
3906                     if (first.sendTime) {
3907                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3908                     }
3909                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3910                     if (firstMove && !bookHit) {
3911                         firstMove = FALSE;
3912                         if (first.useColors) {
3913                           SendToProgram(gameMode == IcsPlayingWhite ?
3914                                         "white\ngo\n" :
3915                                         "black\ngo\n", &first);
3916                         } else {
3917                           SendToProgram("go\n", &first);
3918                         }
3919                         first.maybeThinking = TRUE;
3920                     }
3921                 }
3922             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3923               if (moveList[moveNum - 1][0] == NULLCHAR) {
3924                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3925                 DisplayError(str, 0);
3926               } else {
3927                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3928                 SendMoveToProgram(moveNum - 1, &first);
3929               }
3930             }
3931         }
3932 #endif
3933     }
3934
3935     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3936         /* If move comes from a remote source, animate it.  If it
3937            isn't remote, it will have already been animated. */
3938         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3939             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3940         }
3941         if (!pausing && appData.highlightLastMove) {
3942             SetHighlights(fromX, fromY, toX, toY);
3943         }
3944     }
3945     
3946     /* Start the clocks */
3947     whiteFlag = blackFlag = FALSE;
3948     appData.clockMode = !(basetime == 0 && increment == 0);
3949     if (ticking == 0) {
3950       ics_clock_paused = TRUE;
3951       StopClocks();
3952     } else if (ticking == 1) {
3953       ics_clock_paused = FALSE;
3954     }
3955     if (gameMode == IcsIdle ||
3956         relation == RELATION_OBSERVING_STATIC ||
3957         relation == RELATION_EXAMINING ||
3958         ics_clock_paused)
3959       DisplayBothClocks();
3960     else
3961       StartClocks();
3962     
3963     /* Display opponents and material strengths */
3964     if (gameInfo.variant != VariantBughouse &&
3965         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3966         if (tinyLayout || smallLayout) {
3967             if(gameInfo.variant == VariantNormal)
3968                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3969                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3970                     basetime, increment);
3971             else
3972                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3973                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3974                     basetime, increment, (int) gameInfo.variant);
3975         } else {
3976             if(gameInfo.variant == VariantNormal)
3977                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3978                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3979                     basetime, increment);
3980             else
3981                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3982                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3983                     basetime, increment, VariantName(gameInfo.variant));
3984         }
3985         DisplayTitle(str);
3986   if (appData.debugMode) {
3987     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3988   }
3989     }
3990
3991    
3992     /* Display the board */
3993     if (!pausing && !appData.noGUI) {
3994       
3995       if (appData.premove)
3996           if (!gotPremove || 
3997              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3998              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3999               ClearPremoveHighlights();
4000
4001       DrawPosition(FALSE, boards[currentMove]);
4002       DisplayMove(moveNum - 1);
4003       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4004             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4005               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4006         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4007       }
4008     }
4009
4010     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4011 #if ZIPPY
4012     if(bookHit) { // [HGM] book: simulate book reply
4013         static char bookMove[MSG_SIZ]; // a bit generous?
4014
4015         programStats.nodes = programStats.depth = programStats.time = 
4016         programStats.score = programStats.got_only_move = 0;
4017         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4018
4019         strcpy(bookMove, "move ");
4020         strcat(bookMove, bookHit);
4021         HandleMachineMove(bookMove, &first);
4022     }
4023 #endif
4024 }
4025
4026 void
4027 GetMoveListEvent()
4028 {
4029     char buf[MSG_SIZ];
4030     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4031         ics_getting_history = H_REQUESTED;
4032         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4033         SendToICS(buf);
4034     }
4035 }
4036
4037 void
4038 AnalysisPeriodicEvent(force)
4039      int force;
4040 {
4041     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4042          && !force) || !appData.periodicUpdates)
4043       return;
4044
4045     /* Send . command to Crafty to collect stats */
4046     SendToProgram(".\n", &first);
4047
4048     /* Don't send another until we get a response (this makes
4049        us stop sending to old Crafty's which don't understand
4050        the "." command (sending illegal cmds resets node count & time,
4051        which looks bad)) */
4052     programStats.ok_to_send = 0;
4053 }
4054
4055 void ics_update_width(new_width)
4056         int new_width;
4057 {
4058         ics_printf("set width %d\n", new_width);
4059 }
4060
4061 void
4062 SendMoveToProgram(moveNum, cps)
4063      int moveNum;
4064      ChessProgramState *cps;
4065 {
4066     char buf[MSG_SIZ];
4067
4068     if (cps->useUsermove) {
4069       SendToProgram("usermove ", cps);
4070     }
4071     if (cps->useSAN) {
4072       char *space;
4073       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4074         int len = space - parseList[moveNum];
4075         memcpy(buf, parseList[moveNum], len);
4076         buf[len++] = '\n';
4077         buf[len] = NULLCHAR;
4078       } else {
4079         sprintf(buf, "%s\n", parseList[moveNum]);
4080       }
4081       SendToProgram(buf, cps);
4082     } else {
4083       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4084         AlphaRank(moveList[moveNum], 4);
4085         SendToProgram(moveList[moveNum], cps);
4086         AlphaRank(moveList[moveNum], 4); // and back
4087       } else
4088       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4089        * the engine. It would be nice to have a better way to identify castle 
4090        * moves here. */
4091       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4092                                                                          && cps->useOOCastle) {
4093         int fromX = moveList[moveNum][0] - AAA; 
4094         int fromY = moveList[moveNum][1] - ONE;
4095         int toX = moveList[moveNum][2] - AAA; 
4096         int toY = moveList[moveNum][3] - ONE;
4097         if((boards[moveNum][fromY][fromX] == WhiteKing 
4098             && boards[moveNum][toY][toX] == WhiteRook)
4099            || (boards[moveNum][fromY][fromX] == BlackKing 
4100                && boards[moveNum][toY][toX] == BlackRook)) {
4101           if(toX > fromX) SendToProgram("O-O\n", cps);
4102           else SendToProgram("O-O-O\n", cps);
4103         }
4104         else SendToProgram(moveList[moveNum], cps);
4105       }
4106       else SendToProgram(moveList[moveNum], cps);
4107       /* End of additions by Tord */
4108     }
4109
4110     /* [HGM] setting up the opening has brought engine in force mode! */
4111     /*       Send 'go' if we are in a mode where machine should play. */
4112     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4113         (gameMode == TwoMachinesPlay   ||
4114 #ifdef ZIPPY
4115          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4116 #endif
4117          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4118         SendToProgram("go\n", cps);
4119   if (appData.debugMode) {
4120     fprintf(debugFP, "(extra)\n");
4121   }
4122     }
4123     setboardSpoiledMachineBlack = 0;
4124 }
4125
4126 void
4127 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4128      ChessMove moveType;
4129      int fromX, fromY, toX, toY;
4130 {
4131     char user_move[MSG_SIZ];
4132
4133     switch (moveType) {
4134       default:
4135         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4136                 (int)moveType, fromX, fromY, toX, toY);
4137         DisplayError(user_move + strlen("say "), 0);
4138         break;
4139       case WhiteKingSideCastle:
4140       case BlackKingSideCastle:
4141       case WhiteQueenSideCastleWild:
4142       case BlackQueenSideCastleWild:
4143       /* PUSH Fabien */
4144       case WhiteHSideCastleFR:
4145       case BlackHSideCastleFR:
4146       /* POP Fabien */
4147         sprintf(user_move, "o-o\n");
4148         break;
4149       case WhiteQueenSideCastle:
4150       case BlackQueenSideCastle:
4151       case WhiteKingSideCastleWild:
4152       case BlackKingSideCastleWild:
4153       /* PUSH Fabien */
4154       case WhiteASideCastleFR:
4155       case BlackASideCastleFR:
4156       /* POP Fabien */
4157         sprintf(user_move, "o-o-o\n");
4158         break;
4159       case WhitePromotionQueen:
4160       case BlackPromotionQueen:
4161       case WhitePromotionRook:
4162       case BlackPromotionRook:
4163       case WhitePromotionBishop:
4164       case BlackPromotionBishop:
4165       case WhitePromotionKnight:
4166       case BlackPromotionKnight:
4167       case WhitePromotionKing:
4168       case BlackPromotionKing:
4169       case WhitePromotionChancellor:
4170       case BlackPromotionChancellor:
4171       case WhitePromotionArchbishop:
4172       case BlackPromotionArchbishop:
4173         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4174             sprintf(user_move, "%c%c%c%c=%c\n",
4175                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4176                 PieceToChar(WhiteFerz));
4177         else if(gameInfo.variant == VariantGreat)
4178             sprintf(user_move, "%c%c%c%c=%c\n",
4179                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4180                 PieceToChar(WhiteMan));
4181         else
4182             sprintf(user_move, "%c%c%c%c=%c\n",
4183                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4184                 PieceToChar(PromoPiece(moveType)));
4185         break;
4186       case WhiteDrop:
4187       case BlackDrop:
4188         sprintf(user_move, "%c@%c%c\n",
4189                 ToUpper(PieceToChar((ChessSquare) fromX)),
4190                 AAA + toX, ONE + toY);
4191         break;
4192       case NormalMove:
4193       case WhiteCapturesEnPassant:
4194       case BlackCapturesEnPassant:
4195       case IllegalMove:  /* could be a variant we don't quite understand */
4196         sprintf(user_move, "%c%c%c%c\n",
4197                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4198         break;
4199     }
4200     SendToICS(user_move);
4201     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4202         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4203 }
4204
4205 void
4206 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4207      int rf, ff, rt, ft;
4208      char promoChar;
4209      char move[7];
4210 {
4211     if (rf == DROP_RANK) {
4212         sprintf(move, "%c@%c%c\n",
4213                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4214     } else {
4215         if (promoChar == 'x' || promoChar == NULLCHAR) {
4216             sprintf(move, "%c%c%c%c\n",
4217                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4218         } else {
4219             sprintf(move, "%c%c%c%c%c\n",
4220                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4221         }
4222     }
4223 }
4224
4225 void
4226 ProcessICSInitScript(f)
4227      FILE *f;
4228 {
4229     char buf[MSG_SIZ];
4230
4231     while (fgets(buf, MSG_SIZ, f)) {
4232         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4233     }
4234
4235     fclose(f);
4236 }
4237
4238
4239 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4240 void
4241 AlphaRank(char *move, int n)
4242 {
4243 //    char *p = move, c; int x, y;
4244
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4247     }
4248
4249     if(move[1]=='*' && 
4250        move[2]>='0' && move[2]<='9' &&
4251        move[3]>='a' && move[3]<='x'    ) {
4252         move[1] = '@';
4253         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4254         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4255     } else
4256     if(move[0]>='0' && move[0]<='9' &&
4257        move[1]>='a' && move[1]<='x' &&
4258        move[2]>='0' && move[2]<='9' &&
4259        move[3]>='a' && move[3]<='x'    ) {
4260         /* input move, Shogi -> normal */
4261         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4262         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4263         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4264         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265     } else
4266     if(move[1]=='@' &&
4267        move[3]>='0' && move[3]<='9' &&
4268        move[2]>='a' && move[2]<='x'    ) {
4269         move[1] = '*';
4270         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4271         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4272     } else
4273     if(
4274        move[0]>='a' && move[0]<='x' &&
4275        move[3]>='0' && move[3]<='9' &&
4276        move[2]>='a' && move[2]<='x'    ) {
4277          /* output move, normal -> Shogi */
4278         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4279         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4280         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4281         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4282         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4283     }
4284     if (appData.debugMode) {
4285         fprintf(debugFP, "   out = '%s'\n", move);
4286     }
4287 }
4288
4289 /* Parser for moves from gnuchess, ICS, or user typein box */
4290 Boolean
4291 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4292      char *move;
4293      int moveNum;
4294      ChessMove *moveType;
4295      int *fromX, *fromY, *toX, *toY;
4296      char *promoChar;
4297 {       
4298     if (appData.debugMode) {
4299         fprintf(debugFP, "move to parse: %s\n", move);
4300     }
4301     *moveType = yylexstr(moveNum, move);
4302
4303     switch (*moveType) {
4304       case WhitePromotionChancellor:
4305       case BlackPromotionChancellor:
4306       case WhitePromotionArchbishop:
4307       case BlackPromotionArchbishop:
4308       case WhitePromotionQueen:
4309       case BlackPromotionQueen:
4310       case WhitePromotionRook:
4311       case BlackPromotionRook:
4312       case WhitePromotionBishop:
4313       case BlackPromotionBishop:
4314       case WhitePromotionKnight:
4315       case BlackPromotionKnight:
4316       case WhitePromotionKing:
4317       case BlackPromotionKing:
4318       case NormalMove:
4319       case WhiteCapturesEnPassant:
4320       case BlackCapturesEnPassant:
4321       case WhiteKingSideCastle:
4322       case WhiteQueenSideCastle:
4323       case BlackKingSideCastle:
4324       case BlackQueenSideCastle:
4325       case WhiteKingSideCastleWild:
4326       case WhiteQueenSideCastleWild:
4327       case BlackKingSideCastleWild:
4328       case BlackQueenSideCastleWild:
4329       /* Code added by Tord: */
4330       case WhiteHSideCastleFR:
4331       case WhiteASideCastleFR:
4332       case BlackHSideCastleFR:
4333       case BlackASideCastleFR:
4334       /* End of code added by Tord */
4335       case IllegalMove:         /* bug or odd chess variant */
4336         *fromX = currentMoveString[0] - AAA;
4337         *fromY = currentMoveString[1] - ONE;
4338         *toX = currentMoveString[2] - AAA;
4339         *toY = currentMoveString[3] - ONE;
4340         *promoChar = currentMoveString[4];
4341         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4342             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4343     if (appData.debugMode) {
4344         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4345     }
4346             *fromX = *fromY = *toX = *toY = 0;
4347             return FALSE;
4348         }
4349         if (appData.testLegality) {
4350           return (*moveType != IllegalMove);
4351         } else {
4352           return !(fromX == fromY && toX == toY);
4353         }
4354
4355       case WhiteDrop:
4356       case BlackDrop:
4357         *fromX = *moveType == WhiteDrop ?
4358           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4359           (int) CharToPiece(ToLower(currentMoveString[0]));
4360         *fromY = DROP_RANK;
4361         *toX = currentMoveString[2] - AAA;
4362         *toY = currentMoveString[3] - ONE;
4363         *promoChar = NULLCHAR;
4364         return TRUE;
4365
4366       case AmbiguousMove:
4367       case ImpossibleMove:
4368       case (ChessMove) 0:       /* end of file */
4369       case ElapsedTime:
4370       case Comment:
4371       case PGNTag:
4372       case NAG:
4373       case WhiteWins:
4374       case BlackWins:
4375       case GameIsDrawn:
4376       default:
4377     if (appData.debugMode) {
4378         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4379     }
4380         /* bug? */
4381         *fromX = *fromY = *toX = *toY = 0;
4382         *promoChar = NULLCHAR;
4383         return FALSE;
4384     }
4385 }
4386
4387 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4388 // All positions will have equal probability, but the current method will not provide a unique
4389 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4390 #define DARK 1
4391 #define LITE 2
4392 #define ANY 3
4393
4394 int squaresLeft[4];
4395 int piecesLeft[(int)BlackPawn];
4396 int seed, nrOfShuffles;
4397
4398 void GetPositionNumber()
4399 {       // sets global variable seed
4400         int i;
4401
4402         seed = appData.defaultFrcPosition;
4403         if(seed < 0) { // randomize based on time for negative FRC position numbers
4404                 for(i=0; i<50; i++) seed += random();
4405                 seed = random() ^ random() >> 8 ^ random() << 8;
4406                 if(seed<0) seed = -seed;
4407         }
4408 }
4409
4410 int put(Board board, int pieceType, int rank, int n, int shade)
4411 // put the piece on the (n-1)-th empty squares of the given shade
4412 {
4413         int i;
4414
4415         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4416                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4417                         board[rank][i] = (ChessSquare) pieceType;
4418                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4419                         squaresLeft[ANY]--;
4420                         piecesLeft[pieceType]--; 
4421                         return i;
4422                 }
4423         }
4424         return -1;
4425 }
4426
4427
4428 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4429 // calculate where the next piece goes, (any empty square), and put it there
4430 {
4431         int i;
4432
4433         i = seed % squaresLeft[shade];
4434         nrOfShuffles *= squaresLeft[shade];
4435         seed /= squaresLeft[shade];
4436         put(board, pieceType, rank, i, shade);
4437 }
4438
4439 void AddTwoPieces(Board board, int pieceType, int rank)
4440 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4441 {
4442         int i, n=squaresLeft[ANY], j=n-1, k;
4443
4444         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4445         i = seed % k;  // pick one
4446         nrOfShuffles *= k;
4447         seed /= k;
4448         while(i >= j) i -= j--;
4449         j = n - 1 - j; i += j;
4450         put(board, pieceType, rank, j, ANY);
4451         put(board, pieceType, rank, i, ANY);
4452 }
4453
4454 void SetUpShuffle(Board board, int number)
4455 {
4456         int i, p, first=1;
4457
4458         GetPositionNumber(); nrOfShuffles = 1;
4459
4460         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4461         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4462         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4463
4464         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4465
4466         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4467             p = (int) board[0][i];
4468             if(p < (int) BlackPawn) piecesLeft[p] ++;
4469             board[0][i] = EmptySquare;
4470         }
4471
4472         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4473             // shuffles restricted to allow normal castling put KRR first
4474             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4475                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4476             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4477                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4478             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4479                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4480             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4481                 put(board, WhiteRook, 0, 0, ANY);
4482             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4483         }
4484
4485         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4486             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4487             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4488                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4489                 while(piecesLeft[p] >= 2) {
4490                     AddOnePiece(board, p, 0, LITE);
4491                     AddOnePiece(board, p, 0, DARK);
4492                 }
4493                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4494             }
4495
4496         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4497             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4498             // but we leave King and Rooks for last, to possibly obey FRC restriction
4499             if(p == (int)WhiteRook) continue;
4500             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4501             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4502         }
4503
4504         // now everything is placed, except perhaps King (Unicorn) and Rooks
4505
4506         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4507             // Last King gets castling rights
4508             while(piecesLeft[(int)WhiteUnicorn]) {
4509                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4511             }
4512
4513             while(piecesLeft[(int)WhiteKing]) {
4514                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4515                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4516             }
4517
4518
4519         } else {
4520             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4521             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4522         }
4523
4524         // Only Rooks can be left; simply place them all
4525         while(piecesLeft[(int)WhiteRook]) {
4526                 i = put(board, WhiteRook, 0, 0, ANY);
4527                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4528                         if(first) {
4529                                 first=0;
4530                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4531                         }
4532                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4533                 }
4534         }
4535         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4536             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4537         }
4538
4539         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4540 }
4541
4542 int SetCharTable( char *table, const char * map )
4543 /* [HGM] moved here from winboard.c because of its general usefulness */
4544 /*       Basically a safe strcpy that uses the last character as King */
4545 {
4546     int result = FALSE; int NrPieces;
4547
4548     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4549                     && NrPieces >= 12 && !(NrPieces&1)) {
4550         int i; /* [HGM] Accept even length from 12 to 34 */
4551
4552         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4553         for( i=0; i<NrPieces/2-1; i++ ) {
4554             table[i] = map[i];
4555             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4556         }
4557         table[(int) WhiteKing]  = map[NrPieces/2-1];
4558         table[(int) BlackKing]  = map[NrPieces-1];
4559
4560         result = TRUE;
4561     }
4562
4563     return result;
4564 }
4565
4566 void Prelude(Board board)
4567 {       // [HGM] superchess: random selection of exo-pieces
4568         int i, j, k; ChessSquare p; 
4569         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4570
4571         GetPositionNumber(); // use FRC position number
4572
4573         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4574             SetCharTable(pieceToChar, appData.pieceToCharTable);
4575             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4576                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4577         }
4578
4579         j = seed%4;                 seed /= 4; 
4580         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4581         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4582         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4583         j = seed%3 + (seed%3 >= j); seed /= 3; 
4584         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4585         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4586         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4587         j = seed%3;                 seed /= 3; 
4588         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4589         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4590         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4591         j = seed%2 + (seed%2 >= j); seed /= 2; 
4592         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4593         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4594         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4595         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4596         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4597         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4598         put(board, exoPieces[0],    0, 0, ANY);
4599         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4600 }
4601
4602 void
4603 InitPosition(redraw)
4604      int redraw;
4605 {
4606     ChessSquare (* pieces)[BOARD_SIZE];
4607     int i, j, pawnRow, overrule,
4608     oldx = gameInfo.boardWidth,
4609     oldy = gameInfo.boardHeight,
4610     oldh = gameInfo.holdingsWidth,
4611     oldv = gameInfo.variant;
4612
4613     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4614
4615     /* [AS] Initialize pv info list [HGM] and game status */
4616     {
4617         for( i=0; i<MAX_MOVES; i++ ) {
4618             pvInfoList[i].depth = 0;
4619             epStatus[i]=EP_NONE;
4620             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4621         }
4622
4623         initialRulePlies = 0; /* 50-move counter start */
4624
4625         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4626         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4627     }
4628
4629     
4630     /* [HGM] logic here is completely changed. In stead of full positions */
4631     /* the initialized data only consist of the two backranks. The switch */
4632     /* selects which one we will use, which is than copied to the Board   */
4633     /* initialPosition, which for the rest is initialized by Pawns and    */
4634     /* empty squares. This initial position is then copied to boards[0],  */
4635     /* possibly after shuffling, so that it remains available.            */
4636
4637     gameInfo.holdingsWidth = 0; /* default board sizes */
4638     gameInfo.boardWidth    = 8;
4639     gameInfo.boardHeight   = 8;
4640     gameInfo.holdingsSize  = 0;
4641     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4642     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4643     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4644
4645     switch (gameInfo.variant) {
4646     case VariantFischeRandom:
4647       shuffleOpenings = TRUE;
4648     default:
4649       pieces = FIDEArray;
4650       break;
4651     case VariantShatranj:
4652       pieces = ShatranjArray;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4655       break;
4656     case VariantTwoKings:
4657       pieces = twoKingsArray;
4658       break;
4659     case VariantCapaRandom:
4660       shuffleOpenings = TRUE;
4661     case VariantCapablanca:
4662       pieces = CapablancaArray;
4663       gameInfo.boardWidth = 10;
4664       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4665       break;
4666     case VariantGothic:
4667       pieces = GothicArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4670       break;
4671     case VariantJanus:
4672       pieces = JanusArray;
4673       gameInfo.boardWidth = 10;
4674       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4675       nrCastlingRights = 6;
4676         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4677         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4678         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4679         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4680         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4681         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4682       break;
4683     case VariantFalcon:
4684       pieces = FalconArray;
4685       gameInfo.boardWidth = 10;
4686       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4687       break;
4688     case VariantXiangqi:
4689       pieces = XiangqiArray;
4690       gameInfo.boardWidth  = 9;
4691       gameInfo.boardHeight = 10;
4692       nrCastlingRights = 0;
4693       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4694       break;
4695     case VariantShogi:
4696       pieces = ShogiArray;
4697       gameInfo.boardWidth  = 9;
4698       gameInfo.boardHeight = 9;
4699       gameInfo.holdingsSize = 7;
4700       nrCastlingRights = 0;
4701       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4702       break;
4703     case VariantCourier:
4704       pieces = CourierArray;
4705       gameInfo.boardWidth  = 12;
4706       nrCastlingRights = 0;
4707       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4708       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4709       break;
4710     case VariantKnightmate:
4711       pieces = KnightmateArray;
4712       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4713       break;
4714     case VariantFairy:
4715       pieces = fairyArray;
4716       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4717       break;
4718     case VariantGreat:
4719       pieces = GreatArray;
4720       gameInfo.boardWidth = 10;
4721       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4722       gameInfo.holdingsSize = 8;
4723       break;
4724     case VariantSuper:
4725       pieces = FIDEArray;
4726       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4727       gameInfo.holdingsSize = 8;
4728       startedFromSetupPosition = TRUE;
4729       break;
4730     case VariantCrazyhouse:
4731     case VariantBughouse:
4732       pieces = FIDEArray;
4733       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4734       gameInfo.holdingsSize = 5;
4735       break;
4736     case VariantWildCastle:
4737       pieces = FIDEArray;
4738       /* !!?shuffle with kings guaranteed to be on d or e file */
4739       shuffleOpenings = 1;
4740       break;
4741     case VariantNoCastle:
4742       pieces = FIDEArray;
4743       nrCastlingRights = 0;
4744       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4745       /* !!?unconstrained back-rank shuffle */
4746       shuffleOpenings = 1;
4747       break;
4748     }
4749
4750     overrule = 0;
4751     if(appData.NrFiles >= 0) {
4752         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4753         gameInfo.boardWidth = appData.NrFiles;
4754     }
4755     if(appData.NrRanks >= 0) {
4756         gameInfo.boardHeight = appData.NrRanks;
4757     }
4758     if(appData.holdingsSize >= 0) {
4759         i = appData.holdingsSize;
4760         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4761         gameInfo.holdingsSize = i;
4762     }
4763     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4764     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4765         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4766
4767     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4768     if(pawnRow < 1) pawnRow = 1;
4769
4770     /* User pieceToChar list overrules defaults */
4771     if(appData.pieceToCharTable != NULL)
4772         SetCharTable(pieceToChar, appData.pieceToCharTable);
4773
4774     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4775
4776         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4777             s = (ChessSquare) 0; /* account holding counts in guard band */
4778         for( i=0; i<BOARD_HEIGHT; i++ )
4779             initialPosition[i][j] = s;
4780
4781         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4782         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4783         initialPosition[pawnRow][j] = WhitePawn;
4784         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4785         if(gameInfo.variant == VariantXiangqi) {
4786             if(j&1) {
4787                 initialPosition[pawnRow][j] = 
4788                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4789                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4790                    initialPosition[2][j] = WhiteCannon;
4791                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4792                 }
4793             }
4794         }
4795         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4796     }
4797     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4798
4799             j=BOARD_LEFT+1;
4800             initialPosition[1][j] = WhiteBishop;
4801             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4802             j=BOARD_RGHT-2;
4803             initialPosition[1][j] = WhiteRook;
4804             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4805     }
4806
4807     if( nrCastlingRights == -1) {
4808         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4809         /*       This sets default castling rights from none to normal corners   */
4810         /* Variants with other castling rights must set them themselves above    */
4811         nrCastlingRights = 6;
4812        
4813         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4814         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4815         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4816         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4817         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4818         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4819      }
4820
4821      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4822      if(gameInfo.variant == VariantGreat) { // promotion commoners
4823         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4824         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4825         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4826         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4827      }
4828   if (appData.debugMode) {
4829     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4830   }
4831     if(shuffleOpenings) {
4832         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4833         startedFromSetupPosition = TRUE;
4834     }
4835     if(startedFromPositionFile) {
4836       /* [HGM] loadPos: use PositionFile for every new game */
4837       CopyBoard(initialPosition, filePosition);
4838       for(i=0; i<nrCastlingRights; i++)
4839           castlingRights[0][i] = initialRights[i] = fileRights[i];
4840       startedFromSetupPosition = TRUE;
4841     }
4842
4843     CopyBoard(boards[0], initialPosition);
4844
4845     if(oldx != gameInfo.boardWidth ||
4846        oldy != gameInfo.boardHeight ||
4847        oldh != gameInfo.holdingsWidth
4848 #ifdef GOTHIC
4849        || oldv == VariantGothic ||        // For licensing popups
4850        gameInfo.variant == VariantGothic
4851 #endif
4852 #ifdef FALCON
4853        || oldv == VariantFalcon ||
4854        gameInfo.variant == VariantFalcon
4855 #endif
4856                                          )
4857             InitDrawingSizes(-2 ,0);
4858
4859     if (redraw)
4860       DrawPosition(TRUE, boards[currentMove]);
4861 }
4862
4863 void
4864 SendBoard(cps, moveNum)
4865      ChessProgramState *cps;
4866      int moveNum;
4867 {
4868     char message[MSG_SIZ];
4869     
4870     if (cps->useSetboard) {
4871       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4872       sprintf(message, "setboard %s\n", fen);
4873       SendToProgram(message, cps);
4874       free(fen);
4875
4876     } else {
4877       ChessSquare *bp;
4878       int i, j;
4879       /* Kludge to set black to move, avoiding the troublesome and now
4880        * deprecated "black" command.
4881        */
4882       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4883
4884       SendToProgram("edit\n", cps);
4885       SendToProgram("#\n", cps);
4886       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4887         bp = &boards[moveNum][i][BOARD_LEFT];
4888         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4889           if ((int) *bp < (int) BlackPawn) {
4890             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4891                     AAA + j, ONE + i);
4892             if(message[0] == '+' || message[0] == '~') {
4893                 sprintf(message, "%c%c%c+\n",
4894                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4895                         AAA + j, ONE + i);
4896             }
4897             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4898                 message[1] = BOARD_RGHT   - 1 - j + '1';
4899                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4900             }
4901             SendToProgram(message, cps);
4902           }
4903         }
4904       }
4905     
4906       SendToProgram("c\n", cps);
4907       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4908         bp = &boards[moveNum][i][BOARD_LEFT];
4909         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4910           if (((int) *bp != (int) EmptySquare)
4911               && ((int) *bp >= (int) BlackPawn)) {
4912             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4913                     AAA + j, ONE + i);
4914             if(message[0] == '+' || message[0] == '~') {
4915                 sprintf(message, "%c%c%c+\n",
4916                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4917                         AAA + j, ONE + i);
4918             }
4919             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4920                 message[1] = BOARD_RGHT   - 1 - j + '1';
4921                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4922             }
4923             SendToProgram(message, cps);
4924           }
4925         }
4926       }
4927     
4928       SendToProgram(".\n", cps);
4929     }
4930     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4931 }
4932
4933 int
4934 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4935 {
4936     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4937     /* [HGM] add Shogi promotions */
4938     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4939     ChessSquare piece;
4940     ChessMove moveType;
4941     Boolean premove;
4942
4943     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4944     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4945
4946     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4947       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4948         return FALSE;
4949
4950     piece = boards[currentMove][fromY][fromX];
4951     if(gameInfo.variant == VariantShogi) {
4952         promotionZoneSize = 3;
4953         highestPromotingPiece = (int)WhiteFerz;
4954     }
4955
4956     // next weed out all moves that do not touch the promotion zone at all
4957     if((int)piece >= BlackPawn) {
4958         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4959              return FALSE;
4960         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4961     } else {
4962         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4963            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4964     }
4965
4966     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4967
4968     // weed out mandatory Shogi promotions
4969     if(gameInfo.variant == VariantShogi) {
4970         if(piece >= BlackPawn) {
4971             if(toY == 0 && piece == BlackPawn ||
4972                toY == 0 && piece == BlackQueen ||
4973                toY <= 1 && piece == BlackKnight) {
4974                 *promoChoice = '+';
4975                 return FALSE;
4976             }
4977         } else {
4978             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4979                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4980                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4981                 *promoChoice = '+';
4982                 return FALSE;
4983             }
4984         }
4985     }
4986
4987     // weed out obviously illegal Pawn moves
4988     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4989         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4990         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4991         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4992         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4993         // note we are not allowed to test for valid (non-)capture, due to premove
4994     }
4995
4996     // we either have a choice what to promote to, or (in Shogi) whether to promote
4997     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4998         *promoChoice = PieceToChar(BlackFerz);  // no choice
4999         return FALSE;
5000     }
5001     if(appData.alwaysPromoteToQueen) { // predetermined
5002         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5003              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5004         else *promoChoice = PieceToChar(BlackQueen);
5005         return FALSE;
5006     }
5007
5008     // suppress promotion popup on illegal moves that are not premoves
5009     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5010               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5011     if(appData.testLegality && !premove) {
5012         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5013                         epStatus[currentMove], castlingRights[currentMove],
5014                         fromY, fromX, toY, toX, NULLCHAR);
5015         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5016            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5017             return FALSE;
5018     }
5019
5020     return TRUE;
5021 }
5022
5023 int
5024 InPalace(row, column)
5025      int row, column;
5026 {   /* [HGM] for Xiangqi */
5027     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5028          column < (BOARD_WIDTH + 4)/2 &&
5029          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5030     return FALSE;
5031 }
5032
5033 int
5034 PieceForSquare (x, y)
5035      int x;
5036      int y;
5037 {
5038   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5039      return -1;
5040   else
5041      return boards[currentMove][y][x];
5042 }
5043
5044 int
5045 OKToStartUserMove(x, y)
5046      int x, y;
5047 {
5048     ChessSquare from_piece;
5049     int white_piece;
5050
5051     if (matchMode) return FALSE;
5052     if (gameMode == EditPosition) return TRUE;
5053
5054     if (x >= 0 && y >= 0)
5055       from_piece = boards[currentMove][y][x];
5056     else
5057       from_piece = EmptySquare;
5058
5059     if (from_piece == EmptySquare) return FALSE;
5060
5061     white_piece = (int)from_piece >= (int)WhitePawn &&
5062       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5063
5064     switch (gameMode) {
5065       case PlayFromGameFile:
5066       case AnalyzeFile:
5067       case TwoMachinesPlay:
5068       case EndOfGame:
5069         return FALSE;
5070
5071       case IcsObserving:
5072       case IcsIdle:
5073         return FALSE;
5074
5075       case MachinePlaysWhite:
5076       case IcsPlayingBlack:
5077         if (appData.zippyPlay) return FALSE;
5078         if (white_piece) {
5079             DisplayMoveError(_("You are playing Black"));
5080             return FALSE;
5081         }
5082         break;
5083
5084       case MachinePlaysBlack:
5085       case IcsPlayingWhite:
5086         if (appData.zippyPlay) return FALSE;
5087         if (!white_piece) {
5088             DisplayMoveError(_("You are playing White"));
5089             return FALSE;
5090         }
5091         break;
5092
5093       case EditGame:
5094         if (!white_piece && WhiteOnMove(currentMove)) {
5095             DisplayMoveError(_("It is White's turn"));
5096             return FALSE;
5097         }           
5098         if (white_piece && !WhiteOnMove(currentMove)) {
5099             DisplayMoveError(_("It is Black's turn"));
5100             return FALSE;
5101         }           
5102         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5103             /* Editing correspondence game history */
5104             /* Could disallow this or prompt for confirmation */
5105             cmailOldMove = -1;
5106         }
5107         if (currentMove < forwardMostMove) {
5108             /* Discarding moves */
5109             /* Could prompt for confirmation here,
5110                but I don't think that's such a good idea */
5111             forwardMostMove = currentMove;
5112         }
5113         break;
5114
5115       case BeginningOfGame:
5116         if (appData.icsActive) return FALSE;
5117         if (!appData.noChessProgram) {
5118             if (!white_piece) {
5119                 DisplayMoveError(_("You are playing White"));
5120                 return FALSE;
5121             }
5122         }
5123         break;
5124         
5125       case Training:
5126         if (!white_piece && WhiteOnMove(currentMove)) {
5127             DisplayMoveError(_("It is White's turn"));
5128             return FALSE;
5129         }           
5130         if (white_piece && !WhiteOnMove(currentMove)) {
5131             DisplayMoveError(_("It is Black's turn"));
5132             return FALSE;
5133         }           
5134         break;
5135
5136       default:
5137       case IcsExamining:
5138         break;
5139     }
5140     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5141         && gameMode != AnalyzeFile && gameMode != Training) {
5142         DisplayMoveError(_("Displayed position is not current"));
5143         return FALSE;
5144     }
5145     return TRUE;
5146 }
5147
5148 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5149 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5150 int lastLoadGameUseList = FALSE;
5151 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5152 ChessMove lastLoadGameStart = (ChessMove) 0;
5153
5154 ChessMove
5155 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5156      int fromX, fromY, toX, toY;
5157      int promoChar;
5158      Boolean captureOwn;
5159 {
5160     ChessMove moveType;
5161     ChessSquare pdown, pup;
5162
5163     /* Check if the user is playing in turn.  This is complicated because we
5164        let the user "pick up" a piece before it is his turn.  So the piece he
5165        tried to pick up may have been captured by the time he puts it down!
5166        Therefore we use the color the user is supposed to be playing in this
5167        test, not the color of the piece that is currently on the starting
5168        square---except in EditGame mode, where the user is playing both
5169        sides; fortunately there the capture race can't happen.  (It can
5170        now happen in IcsExamining mode, but that's just too bad.  The user
5171        will get a somewhat confusing message in that case.)
5172        */
5173
5174     switch (gameMode) {
5175       case PlayFromGameFile:
5176       case AnalyzeFile:
5177       case TwoMachinesPlay:
5178       case EndOfGame:
5179       case IcsObserving:
5180       case IcsIdle:
5181         /* We switched into a game mode where moves are not accepted,
5182            perhaps while the mouse button was down. */
5183         return ImpossibleMove;
5184
5185       case MachinePlaysWhite:
5186         /* User is moving for Black */
5187         if (WhiteOnMove(currentMove)) {
5188             DisplayMoveError(_("It is White's turn"));
5189             return ImpossibleMove;
5190         }
5191         break;
5192
5193       case MachinePlaysBlack:
5194         /* User is moving for White */
5195         if (!WhiteOnMove(currentMove)) {
5196             DisplayMoveError(_("It is Black's turn"));
5197             return ImpossibleMove;
5198         }
5199         break;
5200
5201       case EditGame:
5202       case IcsExamining:
5203       case BeginningOfGame:
5204       case AnalyzeMode:
5205       case Training:
5206         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5207             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5208             /* User is moving for Black */
5209             if (WhiteOnMove(currentMove)) {
5210                 DisplayMoveError(_("It is White's turn"));
5211                 return ImpossibleMove;
5212             }
5213         } else {
5214             /* User is moving for White */
5215             if (!WhiteOnMove(currentMove)) {
5216                 DisplayMoveError(_("It is Black's turn"));
5217                 return ImpossibleMove;
5218             }
5219         }
5220         break;
5221
5222       case IcsPlayingBlack:
5223         /* User is moving for Black */
5224         if (WhiteOnMove(currentMove)) {
5225             if (!appData.premove) {
5226                 DisplayMoveError(_("It is White's turn"));
5227             } else if (toX >= 0 && toY >= 0) {
5228                 premoveToX = toX;
5229                 premoveToY = toY;
5230                 premoveFromX = fromX;
5231                 premoveFromY = fromY;
5232                 premovePromoChar = promoChar;
5233                 gotPremove = 1;
5234                 if (appData.debugMode) 
5235                     fprintf(debugFP, "Got premove: fromX %d,"
5236                             "fromY %d, toX %d, toY %d\n",
5237                             fromX, fromY, toX, toY);
5238             }
5239             return ImpossibleMove;
5240         }
5241         break;
5242
5243       case IcsPlayingWhite:
5244         /* User is moving for White */
5245         if (!WhiteOnMove(currentMove)) {
5246             if (!appData.premove) {
5247                 DisplayMoveError(_("It is Black's turn"));
5248             } else if (toX >= 0 && toY >= 0) {
5249                 premoveToX = toX;
5250                 premoveToY = toY;
5251                 premoveFromX = fromX;
5252                 premoveFromY = fromY;
5253                 premovePromoChar = promoChar;
5254                 gotPremove = 1;
5255                 if (appData.debugMode) 
5256                     fprintf(debugFP, "Got premove: fromX %d,"
5257                             "fromY %d, toX %d, toY %d\n",
5258                             fromX, fromY, toX, toY);
5259             }
5260             return ImpossibleMove;
5261         }
5262         break;
5263
5264       default:
5265         break;
5266
5267       case EditPosition:
5268         /* EditPosition, empty square, or different color piece;
5269            click-click move is possible */
5270         if (toX == -2 || toY == -2) {
5271             boards[0][fromY][fromX] = EmptySquare;
5272             return AmbiguousMove;
5273         } else if (toX >= 0 && toY >= 0) {
5274             boards[0][toY][toX] = boards[0][fromY][fromX];
5275             boards[0][fromY][fromX] = EmptySquare;
5276             return AmbiguousMove;
5277         }
5278         return ImpossibleMove;
5279     }
5280
5281     if(toX < 0 || toY < 0) return ImpossibleMove;
5282     pdown = boards[currentMove][fromY][fromX];
5283     pup = boards[currentMove][toY][toX];
5284
5285     /* [HGM] If move started in holdings, it means a drop */
5286     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5287          if( pup != EmptySquare ) return ImpossibleMove;
5288          if(appData.testLegality) {
5289              /* it would be more logical if LegalityTest() also figured out
5290               * which drops are legal. For now we forbid pawns on back rank.
5291               * Shogi is on its own here...
5292               */
5293              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5294                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5295                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5296          }
5297          return WhiteDrop; /* Not needed to specify white or black yet */
5298     }
5299
5300     userOfferedDraw = FALSE;
5301         
5302     /* [HGM] always test for legality, to get promotion info */
5303     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5304                           epStatus[currentMove], castlingRights[currentMove],
5305                                          fromY, fromX, toY, toX, promoChar);
5306     /* [HGM] but possibly ignore an IllegalMove result */
5307     if (appData.testLegality) {
5308         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5309             DisplayMoveError(_("Illegal move"));
5310             return ImpossibleMove;
5311         }
5312     }
5313 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5314     return moveType;
5315     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5316        function is made into one that returns an OK move type if FinishMove
5317        should be called. This to give the calling driver routine the
5318        opportunity to finish the userMove input with a promotion popup,
5319        without bothering the user with this for invalid or illegal moves */
5320
5321 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5322 }
5323
5324 /* Common tail of UserMoveEvent and DropMenuEvent */
5325 int
5326 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5327      ChessMove moveType;
5328      int fromX, fromY, toX, toY;
5329      /*char*/int promoChar;
5330 {
5331     char *bookHit = 0;
5332 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5333     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5334         // [HGM] superchess: suppress promotions to non-available piece
5335         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5336         if(WhiteOnMove(currentMove)) {
5337             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5338         } else {
5339             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5340         }
5341     }
5342
5343     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5344        move type in caller when we know the move is a legal promotion */
5345     if(moveType == NormalMove && promoChar)
5346         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5347 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5348     /* [HGM] convert drag-and-drop piece drops to standard form */
5349     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5350          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5351            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5352                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5353 //         fromX = boards[currentMove][fromY][fromX];
5354            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5355            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5356            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5357            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5358          fromY = DROP_RANK;
5359     }
5360
5361     /* [HGM] <popupFix> The following if has been moved here from
5362        UserMoveEvent(). Because it seemed to belon here (why not allow
5363        piece drops in training games?), and because it can only be
5364        performed after it is known to what we promote. */
5365     if (gameMode == Training) {
5366       /* compare the move played on the board to the next move in the
5367        * game. If they match, display the move and the opponent's response. 
5368        * If they don't match, display an error message.
5369        */
5370       int saveAnimate;
5371       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5372       CopyBoard(testBoard, boards[currentMove]);
5373       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5374
5375       if (CompareBoards(testBoard, boards[currentMove+1])) {
5376         ForwardInner(currentMove+1);
5377
5378         /* Autoplay the opponent's response.
5379          * if appData.animate was TRUE when Training mode was entered,
5380          * the response will be animated.
5381          */
5382         saveAnimate = appData.animate;
5383         appData.animate = animateTraining;
5384         ForwardInner(currentMove+1);
5385         appData.animate = saveAnimate;
5386
5387         /* check for the end of the game */
5388         if (currentMove >= forwardMostMove) {
5389           gameMode = PlayFromGameFile;
5390           ModeHighlight();
5391           SetTrainingModeOff();
5392           DisplayInformation(_("End of game"));
5393         }
5394       } else {
5395         DisplayError(_("Incorrect move"), 0);
5396       }
5397       return 1;
5398     }
5399
5400   /* Ok, now we know that the move is good, so we can kill
5401      the previous line in Analysis Mode */
5402   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5403     forwardMostMove = currentMove;
5404   }
5405
5406   /* If we need the chess program but it's dead, restart it */
5407   ResurrectChessProgram();
5408
5409   /* A user move restarts a paused game*/
5410   if (pausing)
5411     PauseEvent();
5412
5413   thinkOutput[0] = NULLCHAR;
5414
5415   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5416
5417   if (gameMode == BeginningOfGame) {
5418     if (appData.noChessProgram) {
5419       gameMode = EditGame;
5420       SetGameInfo();
5421     } else {
5422       char buf[MSG_SIZ];
5423       gameMode = MachinePlaysBlack;
5424       StartClocks();
5425       SetGameInfo();
5426       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5427       DisplayTitle(buf);
5428       if (first.sendName) {
5429         sprintf(buf, "name %s\n", gameInfo.white);
5430         SendToProgram(buf, &first);
5431       }
5432       StartClocks();
5433     }
5434     ModeHighlight();
5435   }
5436 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5437   /* Relay move to ICS or chess engine */
5438   if (appData.icsActive) {
5439     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5440         gameMode == IcsExamining) {
5441       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5442       ics_user_moved = 1;
5443     }
5444   } else {
5445     if (first.sendTime && (gameMode == BeginningOfGame ||
5446                            gameMode == MachinePlaysWhite ||
5447                            gameMode == MachinePlaysBlack)) {
5448       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5449     }
5450     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5451          // [HGM] book: if program might be playing, let it use book
5452         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5453         first.maybeThinking = TRUE;
5454     } else SendMoveToProgram(forwardMostMove-1, &first);
5455     if (currentMove == cmailOldMove + 1) {
5456       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5457     }
5458   }
5459
5460   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5461
5462   switch (gameMode) {
5463   case EditGame:
5464     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5465                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5466     case MT_NONE:
5467     case MT_CHECK:
5468       break;
5469     case MT_CHECKMATE:
5470     case MT_STAINMATE:
5471       if (WhiteOnMove(currentMove)) {
5472         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5473       } else {
5474         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5475       }
5476       break;
5477     case MT_STALEMATE:
5478       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5479       break;
5480     }
5481     break;
5482     
5483   case MachinePlaysBlack:
5484   case MachinePlaysWhite:
5485     /* disable certain menu options while machine is thinking */
5486     SetMachineThinkingEnables();
5487     break;
5488
5489   default:
5490     break;
5491   }
5492
5493   if(bookHit) { // [HGM] book: simulate book reply
5494         static char bookMove[MSG_SIZ]; // a bit generous?
5495
5496         programStats.nodes = programStats.depth = programStats.time = 
5497         programStats.score = programStats.got_only_move = 0;
5498         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5499
5500         strcpy(bookMove, "move ");
5501         strcat(bookMove, bookHit);
5502         HandleMachineMove(bookMove, &first);
5503   }
5504   return 1;
5505 }
5506
5507 void
5508 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5509      int fromX, fromY, toX, toY;
5510      int promoChar;
5511 {
5512     /* [HGM] This routine was added to allow calling of its two logical
5513        parts from other modules in the old way. Before, UserMoveEvent()
5514        automatically called FinishMove() if the move was OK, and returned
5515        otherwise. I separated the two, in order to make it possible to
5516        slip a promotion popup in between. But that it always needs two
5517        calls, to the first part, (now called UserMoveTest() ), and to
5518        FinishMove if the first part succeeded. Calls that do not need
5519        to do anything in between, can call this routine the old way. 
5520     */
5521     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5522 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5523     if(moveType == AmbiguousMove)
5524         DrawPosition(FALSE, boards[currentMove]);
5525     else if(moveType != ImpossibleMove && moveType != Comment)
5526         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5527 }
5528
5529 void LeftClick(ClickType clickType, int xPix, int yPix)
5530 {
5531     int x, y;
5532     Boolean saveAnimate;
5533     static int second = 0, promotionChoice = 0;
5534     char promoChoice = NULLCHAR;
5535
5536     if (clickType == Press) ErrorPopDown();
5537
5538     x = EventToSquare(xPix, BOARD_WIDTH);
5539     y = EventToSquare(yPix, BOARD_HEIGHT);
5540     if (!flipView && y >= 0) {
5541         y = BOARD_HEIGHT - 1 - y;
5542     }
5543     if (flipView && x >= 0) {
5544         x = BOARD_WIDTH - 1 - x;
5545     }
5546
5547     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5548         if(clickType == Release) return; // ignore upclick of click-click destination
5549         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5550         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5551         if(gameInfo.holdingsWidth && 
5552                 (WhiteOnMove(currentMove) 
5553                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5554                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5555             // click in right holdings, for determining promotion piece
5556             ChessSquare p = boards[currentMove][y][x];
5557             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5558             if(p != EmptySquare) {
5559                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5560                 fromX = fromY = -1;
5561                 return;
5562             }
5563         }
5564         DrawPosition(FALSE, boards[currentMove]);
5565         return;
5566     }
5567
5568     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5569     if(clickType == Press
5570             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5571               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5572               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5573         return;
5574
5575     if (fromX == -1) {
5576         if (clickType == Press) {
5577             /* First square */
5578             if (OKToStartUserMove(x, y)) {
5579                 fromX = x;
5580                 fromY = y;
5581                 second = 0;
5582                 DragPieceBegin(xPix, yPix);
5583                 if (appData.highlightDragging) {
5584                     SetHighlights(x, y, -1, -1);
5585                 }
5586             }
5587         }
5588         return;
5589     }
5590
5591     /* fromX != -1 */
5592     if (clickType == Press && gameMode != EditPosition) {
5593         ChessSquare fromP;
5594         ChessSquare toP;
5595         int frc;
5596
5597         // ignore off-board to clicks
5598         if(y < 0 || x < 0) return;
5599
5600         /* Check if clicking again on the same color piece */
5601         fromP = boards[currentMove][fromY][fromX];
5602         toP = boards[currentMove][y][x];
5603         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5604         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5605              WhitePawn <= toP && toP <= WhiteKing &&
5606              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5607              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5608             (BlackPawn <= fromP && fromP <= BlackKing && 
5609              BlackPawn <= toP && toP <= BlackKing &&
5610              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5611              !(fromP == BlackKing && toP == BlackRook && frc))) {
5612             /* Clicked again on same color piece -- changed his mind */
5613             second = (x == fromX && y == fromY);
5614             if (appData.highlightDragging) {
5615                 SetHighlights(x, y, -1, -1);
5616             } else {
5617                 ClearHighlights();
5618             }
5619             if (OKToStartUserMove(x, y)) {
5620                 fromX = x;
5621                 fromY = y;
5622                 DragPieceBegin(xPix, yPix);
5623             }
5624             return;
5625         }
5626         // ignore clicks on holdings
5627         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5628     }
5629
5630     if (clickType == Release && x == fromX && y == fromY) {
5631         DragPieceEnd(xPix, yPix);
5632         if (appData.animateDragging) {
5633             /* Undo animation damage if any */
5634             DrawPosition(FALSE, NULL);
5635         }
5636         if (second) {
5637             /* Second up/down in same square; just abort move */
5638             second = 0;
5639             fromX = fromY = -1;
5640             ClearHighlights();
5641             gotPremove = 0;
5642             ClearPremoveHighlights();
5643         } else {
5644             /* First upclick in same square; start click-click mode */
5645             SetHighlights(x, y, -1, -1);
5646         }
5647         return;
5648     }
5649
5650     /* we now have a different from- and (possibly off-board) to-square */
5651     /* Completed move */
5652     toX = x;
5653     toY = y;
5654     saveAnimate = appData.animate;
5655     if (clickType == Press) {
5656         /* Finish clickclick move */
5657         if (appData.animate || appData.highlightLastMove) {
5658             SetHighlights(fromX, fromY, toX, toY);
5659         } else {
5660             ClearHighlights();
5661         }
5662     } else {
5663         /* Finish drag move */
5664         if (appData.highlightLastMove) {
5665             SetHighlights(fromX, fromY, toX, toY);
5666         } else {
5667             ClearHighlights();
5668         }
5669         DragPieceEnd(xPix, yPix);
5670         /* Don't animate move and drag both */
5671         appData.animate = FALSE;
5672     }
5673
5674     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5675     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5676         ClearHighlights();
5677         fromX = fromY = -1;
5678         DrawPosition(TRUE, NULL);
5679         return;
5680     }
5681
5682     // off-board moves should not be highlighted
5683     if(x < 0 || x < 0) ClearHighlights();
5684
5685     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5686         SetHighlights(fromX, fromY, toX, toY);
5687         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5688             // [HGM] super: promotion to captured piece selected from holdings
5689             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5690             promotionChoice = TRUE;
5691             // kludge follows to temporarily execute move on display, without promoting yet
5692             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5693             boards[currentMove][toY][toX] = p;
5694             DrawPosition(FALSE, boards[currentMove]);
5695             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5696             boards[currentMove][toY][toX] = q;
5697             DisplayMessage("Click in holdings to choose piece", "");
5698             return;
5699         }
5700         PromotionPopUp();
5701     } else {
5702         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5703         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5704         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5705         fromX = fromY = -1;
5706     }
5707     appData.animate = saveAnimate;
5708     if (appData.animate || appData.animateDragging) {
5709         /* Undo animation damage if needed */
5710         DrawPosition(FALSE, NULL);
5711     }
5712 }
5713
5714 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5715 {
5716 //    char * hint = lastHint;
5717     FrontEndProgramStats stats;
5718
5719     stats.which = cps == &first ? 0 : 1;
5720     stats.depth = cpstats->depth;
5721     stats.nodes = cpstats->nodes;
5722     stats.score = cpstats->score;
5723     stats.time = cpstats->time;
5724     stats.pv = cpstats->movelist;
5725     stats.hint = lastHint;
5726     stats.an_move_index = 0;
5727     stats.an_move_count = 0;
5728
5729     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5730         stats.hint = cpstats->move_name;
5731         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5732         stats.an_move_count = cpstats->nr_moves;
5733     }
5734
5735     SetProgramStats( &stats );
5736 }
5737
5738 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5739 {   // [HGM] book: this routine intercepts moves to simulate book replies
5740     char *bookHit = NULL;
5741
5742     //first determine if the incoming move brings opponent into his book
5743     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5744         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5745     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5746     if(bookHit != NULL && !cps->bookSuspend) {
5747         // make sure opponent is not going to reply after receiving move to book position
5748         SendToProgram("force\n", cps);
5749         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5750     }
5751     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5752     // now arrange restart after book miss
5753     if(bookHit) {
5754         // after a book hit we never send 'go', and the code after the call to this routine
5755         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5756         char buf[MSG_SIZ];
5757         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5758         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5759         SendToProgram(buf, cps);
5760         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5761     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5762         SendToProgram("go\n", cps);
5763         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5764     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5765         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5766             SendToProgram("go\n", cps); 
5767         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5768     }
5769     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5770 }
5771
5772 char *savedMessage;
5773 ChessProgramState *savedState;
5774 void DeferredBookMove(void)
5775 {
5776         if(savedState->lastPing != savedState->lastPong)
5777                     ScheduleDelayedEvent(DeferredBookMove, 10);
5778         else
5779         HandleMachineMove(savedMessage, savedState);
5780 }
5781
5782 void
5783 HandleMachineMove(message, cps)
5784      char *message;
5785      ChessProgramState *cps;
5786 {
5787     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5788     char realname[MSG_SIZ];
5789     int fromX, fromY, toX, toY;
5790     ChessMove moveType;
5791     char promoChar;
5792     char *p;
5793     int machineWhite;
5794     char *bookHit;
5795
5796 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5797     /*
5798      * Kludge to ignore BEL characters
5799      */
5800     while (*message == '\007') message++;
5801
5802     /*
5803      * [HGM] engine debug message: ignore lines starting with '#' character
5804      */
5805     if(cps->debug && *message == '#') return;
5806
5807     /*
5808      * Look for book output
5809      */
5810     if (cps == &first && bookRequested) {
5811         if (message[0] == '\t' || message[0] == ' ') {
5812             /* Part of the book output is here; append it */
5813             strcat(bookOutput, message);
5814             strcat(bookOutput, "  \n");
5815             return;
5816         } else if (bookOutput[0] != NULLCHAR) {
5817             /* All of book output has arrived; display it */
5818             char *p = bookOutput;
5819             while (*p != NULLCHAR) {
5820                 if (*p == '\t') *p = ' ';
5821                 p++;
5822             }
5823             DisplayInformation(bookOutput);
5824             bookRequested = FALSE;
5825             /* Fall through to parse the current output */
5826         }
5827     }
5828
5829     /*
5830      * Look for machine move.
5831      */
5832     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5833         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5834     {
5835         /* This method is only useful on engines that support ping */
5836         if (cps->lastPing != cps->lastPong) {
5837           if (gameMode == BeginningOfGame) {
5838             /* Extra move from before last new; ignore */
5839             if (appData.debugMode) {
5840                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5841             }
5842           } else {
5843             if (appData.debugMode) {
5844                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5845                         cps->which, gameMode);
5846             }
5847
5848             SendToProgram("undo\n", cps);
5849           }
5850           return;
5851         }
5852
5853         switch (gameMode) {
5854           case BeginningOfGame:
5855             /* Extra move from before last reset; ignore */
5856             if (appData.debugMode) {
5857                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5858             }
5859             return;
5860
5861           case EndOfGame:
5862           case IcsIdle:
5863           default:
5864             /* Extra move after we tried to stop.  The mode test is
5865                not a reliable way of detecting this problem, but it's
5866                the best we can do on engines that don't support ping.
5867             */
5868             if (appData.debugMode) {
5869                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5870                         cps->which, gameMode);
5871             }
5872             SendToProgram("undo\n", cps);
5873             return;
5874
5875           case MachinePlaysWhite:
5876           case IcsPlayingWhite:
5877             machineWhite = TRUE;
5878             break;
5879
5880           case MachinePlaysBlack:
5881           case IcsPlayingBlack:
5882             machineWhite = FALSE;
5883             break;
5884
5885           case TwoMachinesPlay:
5886             machineWhite = (cps->twoMachinesColor[0] == 'w');
5887             break;
5888         }
5889         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5890             if (appData.debugMode) {
5891                 fprintf(debugFP,
5892                         "Ignoring move out of turn by %s, gameMode %d"
5893                         ", forwardMost %d\n",
5894                         cps->which, gameMode, forwardMostMove);
5895             }
5896             return;
5897         }
5898
5899     if (appData.debugMode) { int f = forwardMostMove;
5900         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5901                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5902     }
5903         if(cps->alphaRank) AlphaRank(machineMove, 4);
5904         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5905                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5906             /* Machine move could not be parsed; ignore it. */
5907             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5908                     machineMove, cps->which);
5909             DisplayError(buf1, 0);
5910             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5911                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5912             if (gameMode == TwoMachinesPlay) {
5913               GameEnds(machineWhite ? BlackWins : WhiteWins,
5914                        buf1, GE_XBOARD);
5915             }
5916             return;
5917         }
5918
5919         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5920         /* So we have to redo legality test with true e.p. status here,  */
5921         /* to make sure an illegal e.p. capture does not slip through,   */
5922         /* to cause a forfeit on a justified illegal-move complaint      */
5923         /* of the opponent.                                              */
5924         if( gameMode==TwoMachinesPlay && appData.testLegality
5925             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5926                                                               ) {
5927            ChessMove moveType;
5928            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5929                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5930                              fromY, fromX, toY, toX, promoChar);
5931             if (appData.debugMode) {
5932                 int i;
5933                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5934                     castlingRights[forwardMostMove][i], castlingRank[i]);
5935                 fprintf(debugFP, "castling rights\n");
5936             }
5937             if(moveType == IllegalMove) {
5938                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5939                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5940                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5941                            buf1, GE_XBOARD);
5942                 return;
5943            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5944            /* [HGM] Kludge to handle engines that send FRC-style castling
5945               when they shouldn't (like TSCP-Gothic) */
5946            switch(moveType) {
5947              case WhiteASideCastleFR:
5948              case BlackASideCastleFR:
5949                toX+=2;
5950                currentMoveString[2]++;
5951                break;
5952              case WhiteHSideCastleFR:
5953              case BlackHSideCastleFR:
5954                toX--;
5955                currentMoveString[2]--;
5956                break;
5957              default: ; // nothing to do, but suppresses warning of pedantic compilers
5958            }
5959         }
5960         hintRequested = FALSE;
5961         lastHint[0] = NULLCHAR;
5962         bookRequested = FALSE;
5963         /* Program may be pondering now */
5964         cps->maybeThinking = TRUE;
5965         if (cps->sendTime == 2) cps->sendTime = 1;
5966         if (cps->offeredDraw) cps->offeredDraw--;
5967
5968 #if ZIPPY
5969         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5970             first.initDone) {
5971           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5972           ics_user_moved = 1;
5973           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5974                 char buf[3*MSG_SIZ];
5975
5976                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5977                         programStats.score / 100.,
5978                         programStats.depth,
5979                         programStats.time / 100.,
5980                         (unsigned int)programStats.nodes,
5981                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5982                         programStats.movelist);
5983                 SendToICS(buf);
5984 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5985           }
5986         }
5987 #endif
5988         /* currentMoveString is set as a side-effect of ParseOneMove */
5989         strcpy(machineMove, currentMoveString);
5990         strcat(machineMove, "\n");
5991         strcpy(moveList[forwardMostMove], machineMove);
5992
5993         /* [AS] Save move info and clear stats for next move */
5994         pvInfoList[ forwardMostMove ].score = programStats.score;
5995         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5996         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5997         ClearProgramStats();
5998         thinkOutput[0] = NULLCHAR;
5999         hiddenThinkOutputState = 0;
6000
6001         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6002
6003         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6004         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6005             int count = 0;
6006
6007             while( count < adjudicateLossPlies ) {
6008                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6009
6010                 if( count & 1 ) {
6011                     score = -score; /* Flip score for winning side */
6012                 }
6013
6014                 if( score > adjudicateLossThreshold ) {
6015                     break;
6016                 }
6017
6018                 count++;
6019             }
6020
6021             if( count >= adjudicateLossPlies ) {
6022                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023
6024                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6025                     "Xboard adjudication", 
6026                     GE_XBOARD );
6027
6028                 return;
6029             }
6030         }
6031
6032         if( gameMode == TwoMachinesPlay ) {
6033           // [HGM] some adjudications useful with buggy engines
6034             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6035           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6036
6037
6038             if( appData.testLegality )
6039             {   /* [HGM] Some more adjudications for obstinate engines */
6040                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6041                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6042                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6043                 static int moveCount = 6;
6044                 ChessMove result;
6045                 char *reason = NULL;
6046
6047                 /* Count what is on board. */
6048                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6049                 {   ChessSquare p = boards[forwardMostMove][i][j];
6050                     int m=i;
6051
6052                     switch((int) p)
6053                     {   /* count B,N,R and other of each side */
6054                         case WhiteKing:
6055                         case BlackKing:
6056                              NrK++; break; // [HGM] atomic: count Kings
6057                         case WhiteKnight:
6058                              NrWN++; break;
6059                         case WhiteBishop:
6060                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6061                              bishopsColor |= 1 << ((i^j)&1);
6062                              NrWB++; break;
6063                         case BlackKnight:
6064                              NrBN++; break;
6065                         case BlackBishop:
6066                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6067                              bishopsColor |= 1 << ((i^j)&1);
6068                              NrBB++; break;
6069                         case WhiteRook:
6070                              NrWR++; break;
6071                         case BlackRook:
6072                              NrBR++; break;
6073                         case WhiteQueen:
6074                              NrWQ++; break;
6075                         case BlackQueen:
6076                              NrBQ++; break;
6077                         case EmptySquare: 
6078                              break;
6079                         case BlackPawn:
6080                              m = 7-i;
6081                         case WhitePawn:
6082                              PawnAdvance += m; NrPawns++;
6083                     }
6084                     NrPieces += (p != EmptySquare);
6085                     NrW += ((int)p < (int)BlackPawn);
6086                     if(gameInfo.variant == VariantXiangqi && 
6087                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6088                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6089                         NrW -= ((int)p < (int)BlackPawn);
6090                     }
6091                 }
6092
6093                 /* Some material-based adjudications that have to be made before stalemate test */
6094                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6095                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6096                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6097                      if(appData.checkMates) {
6098                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6099                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6100                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6101                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6102                          return;
6103                      }
6104                 }
6105
6106                 /* Bare King in Shatranj (loses) or Losers (wins) */
6107                 if( NrW == 1 || NrPieces - NrW == 1) {
6108                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6109                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6110                      if(appData.checkMates) {
6111                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6112                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6113                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6114                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6115                          return;
6116                      }
6117                   } else
6118                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6119                   {    /* bare King */
6120                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6121                         if(appData.checkMates) {
6122                             /* but only adjudicate if adjudication enabled */
6123                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6124                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6126                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6127                             return;
6128                         }
6129                   }
6130                 } else bare = 1;
6131
6132
6133             // don't wait for engine to announce game end if we can judge ourselves
6134             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6135                                        castlingRights[forwardMostMove]) ) {
6136               case MT_CHECK:
6137                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6138                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6139                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6140                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6141                             checkCnt++;
6142                         if(checkCnt >= 2) {
6143                             reason = "Xboard adjudication: 3rd check";
6144                             epStatus[forwardMostMove] = EP_CHECKMATE;
6145                             break;
6146                         }
6147                     }
6148                 }
6149               case MT_NONE:
6150               default:
6151                 break;
6152               case MT_STALEMATE:
6153               case MT_STAINMATE:
6154                 reason = "Xboard adjudication: Stalemate";
6155                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6156                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6157                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6158                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6159                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6160                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6161                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6162                                                                         EP_CHECKMATE : EP_WINS);
6163                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6164                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6165                 }
6166                 break;
6167               case MT_CHECKMATE:
6168                 reason = "Xboard adjudication: Checkmate";
6169                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6170                 break;
6171             }
6172
6173                 switch(i = epStatus[forwardMostMove]) {
6174                     case EP_STALEMATE:
6175                         result = GameIsDrawn; break;
6176                     case EP_CHECKMATE:
6177                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6178                     case EP_WINS:
6179                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6180                     default:
6181                         result = (ChessMove) 0;
6182                 }
6183                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6184                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6185                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6186                     GameEnds( result, reason, GE_XBOARD );
6187                     return;
6188                 }
6189
6190                 /* Next absolutely insufficient mating material. */
6191                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6192                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6193                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6194                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6195                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6196
6197                      /* always flag draws, for judging claims */
6198                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6199
6200                      if(appData.materialDraws) {
6201                          /* but only adjudicate them if adjudication enabled */
6202                          SendToProgram("force\n", cps->other); // suppress reply
6203                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6204                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6205                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6206                          return;
6207                      }
6208                 }
6209
6210                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6211                 if(NrPieces == 4 && 
6212                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6213                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6214                    || NrWN==2 || NrBN==2     /* KNNK */
6215                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6216                   ) ) {
6217                      if(--moveCount < 0 && appData.trivialDraws)
6218                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6219                           SendToProgram("force\n", cps->other); // suppress reply
6220                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6221                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6222                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6223                           return;
6224                      }
6225                 } else moveCount = 6;
6226             }
6227           }
6228           
6229           if (appData.debugMode) { int i;
6230             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6231                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6232                     appData.drawRepeats);
6233             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6234               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6235             
6236           }
6237
6238                 /* Check for rep-draws */
6239                 count = 0;
6240                 for(k = forwardMostMove-2;
6241                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6242                         epStatus[k] < EP_UNKNOWN &&
6243                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6244                     k-=2)
6245                 {   int rights=0;
6246                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6247                         /* compare castling rights */
6248                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6249                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6250                                 rights++; /* King lost rights, while rook still had them */
6251                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6252                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6253                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6254                                    rights++; /* but at least one rook lost them */
6255                         }
6256                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6257                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6258                                 rights++; 
6259                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6260                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6261                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6262                                    rights++;
6263                         }
6264                         if( rights == 0 && ++count > appData.drawRepeats-2
6265                             && appData.drawRepeats > 1) {
6266                              /* adjudicate after user-specified nr of repeats */
6267                              SendToProgram("force\n", cps->other); // suppress reply
6268                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6269                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6270                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6271                                 // [HGM] xiangqi: check for forbidden perpetuals
6272                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6273                                 for(m=forwardMostMove; m>k; m-=2) {
6274                                     if(MateTest(boards[m], PosFlags(m), 
6275                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6276                                         ourPerpetual = 0; // the current mover did not always check
6277                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6278                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6279                                         hisPerpetual = 0; // the opponent did not always check
6280                                 }
6281                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6282                                                                         ourPerpetual, hisPerpetual);
6283                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6284                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6285                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6286                                     return;
6287                                 }
6288                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6289                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6290                                 // Now check for perpetual chases
6291                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6292                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6293                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6294                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6295                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6296                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6297                                         return;
6298                                     }
6299                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6300                                         break; // Abort repetition-checking loop.
6301                                 }
6302                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6303                              }
6304                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6305                              return;
6306                         }
6307                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6308                              epStatus[forwardMostMove] = EP_REP_DRAW;
6309                     }
6310                 }
6311
6312                 /* Now we test for 50-move draws. Determine ply count */
6313                 count = forwardMostMove;
6314                 /* look for last irreversble move */
6315                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6316                     count--;
6317                 /* if we hit starting position, add initial plies */
6318                 if( count == backwardMostMove )
6319                     count -= initialRulePlies;
6320                 count = forwardMostMove - count; 
6321                 if( count >= 100)
6322                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6323                          /* this is used to judge if draw claims are legal */
6324                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6325                          SendToProgram("force\n", cps->other); // suppress reply
6326                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6327                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6329                          return;
6330                 }
6331
6332                 /* if draw offer is pending, treat it as a draw claim
6333                  * when draw condition present, to allow engines a way to
6334                  * claim draws before making their move to avoid a race
6335                  * condition occurring after their move
6336                  */
6337                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6338                          char *p = NULL;
6339                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6340                              p = "Draw claim: 50-move rule";
6341                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6342                              p = "Draw claim: 3-fold repetition";
6343                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6344                              p = "Draw claim: insufficient mating material";
6345                          if( p != NULL ) {
6346                              SendToProgram("force\n", cps->other); // suppress reply
6347                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6348                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6349                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350                              return;
6351                          }
6352                 }
6353
6354
6355                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6356                     SendToProgram("force\n", cps->other); // suppress reply
6357                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6358                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6359
6360                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6361
6362                     return;
6363                 }
6364         }
6365
6366         bookHit = NULL;
6367         if (gameMode == TwoMachinesPlay) {
6368             /* [HGM] relaying draw offers moved to after reception of move */
6369             /* and interpreting offer as claim if it brings draw condition */
6370             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6371                 SendToProgram("draw\n", cps->other);
6372             }
6373             if (cps->other->sendTime) {
6374                 SendTimeRemaining(cps->other,
6375                                   cps->other->twoMachinesColor[0] == 'w');
6376             }
6377             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6378             if (firstMove && !bookHit) {
6379                 firstMove = FALSE;
6380                 if (cps->other->useColors) {
6381                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6382                 }
6383                 SendToProgram("go\n", cps->other);
6384             }
6385             cps->other->maybeThinking = TRUE;
6386         }
6387
6388         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389         
6390         if (!pausing && appData.ringBellAfterMoves) {
6391             RingBell();
6392         }
6393
6394         /* 
6395          * Reenable menu items that were disabled while
6396          * machine was thinking
6397          */
6398         if (gameMode != TwoMachinesPlay)
6399             SetUserThinkingEnables();
6400
6401         // [HGM] book: after book hit opponent has received move and is now in force mode
6402         // force the book reply into it, and then fake that it outputted this move by jumping
6403         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6404         if(bookHit) {
6405                 static char bookMove[MSG_SIZ]; // a bit generous?
6406
6407                 strcpy(bookMove, "move ");
6408                 strcat(bookMove, bookHit);
6409                 message = bookMove;
6410                 cps = cps->other;
6411                 programStats.nodes = programStats.depth = programStats.time = 
6412                 programStats.score = programStats.got_only_move = 0;
6413                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6414
6415                 if(cps->lastPing != cps->lastPong) {
6416                     savedMessage = message; // args for deferred call
6417                     savedState = cps;
6418                     ScheduleDelayedEvent(DeferredBookMove, 10);
6419                     return;
6420                 }
6421                 goto FakeBookMove;
6422         }
6423
6424         return;
6425     }
6426
6427     /* Set special modes for chess engines.  Later something general
6428      *  could be added here; for now there is just one kludge feature,
6429      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6430      *  when "xboard" is given as an interactive command.
6431      */
6432     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6433         cps->useSigint = FALSE;
6434         cps->useSigterm = FALSE;
6435     }
6436     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6437       ParseFeatures(message+8, cps);
6438       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6439     }
6440
6441     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6442      * want this, I was asked to put it in, and obliged.
6443      */
6444     if (!strncmp(message, "setboard ", 9)) {
6445         Board initial_position; int i;
6446
6447         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6448
6449         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6450             DisplayError(_("Bad FEN received from engine"), 0);
6451             return ;
6452         } else {
6453            Reset(TRUE, FALSE);
6454            CopyBoard(boards[0], initial_position);
6455            initialRulePlies = FENrulePlies;
6456            epStatus[0] = FENepStatus;
6457            for( i=0; i<nrCastlingRights; i++ )
6458                 castlingRights[0][i] = FENcastlingRights[i];
6459            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6460            else gameMode = MachinePlaysBlack;                 
6461            DrawPosition(FALSE, boards[currentMove]);
6462         }
6463         return;
6464     }
6465
6466     /*
6467      * Look for communication commands
6468      */
6469     if (!strncmp(message, "telluser ", 9)) {
6470         DisplayNote(message + 9);
6471         return;
6472     }
6473     if (!strncmp(message, "tellusererror ", 14)) {
6474         DisplayError(message + 14, 0);
6475         return;
6476     }
6477     if (!strncmp(message, "tellopponent ", 13)) {
6478       if (appData.icsActive) {
6479         if (loggedOn) {
6480           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6481           SendToICS(buf1);
6482         }
6483       } else {
6484         DisplayNote(message + 13);
6485       }
6486       return;
6487     }
6488     if (!strncmp(message, "tellothers ", 11)) {
6489       if (appData.icsActive) {
6490         if (loggedOn) {
6491           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6492           SendToICS(buf1);
6493         }
6494       }
6495       return;
6496     }
6497     if (!strncmp(message, "tellall ", 8)) {
6498       if (appData.icsActive) {
6499         if (loggedOn) {
6500           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6501           SendToICS(buf1);
6502         }
6503       } else {
6504         DisplayNote(message + 8);
6505       }
6506       return;
6507     }
6508     if (strncmp(message, "warning", 7) == 0) {
6509         /* Undocumented feature, use tellusererror in new code */
6510         DisplayError(message, 0);
6511         return;
6512     }
6513     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6514         strcpy(realname, cps->tidy);
6515         strcat(realname, " query");
6516         AskQuestion(realname, buf2, buf1, cps->pr);
6517         return;
6518     }
6519     /* Commands from the engine directly to ICS.  We don't allow these to be 
6520      *  sent until we are logged on. Crafty kibitzes have been known to 
6521      *  interfere with the login process.
6522      */
6523     if (loggedOn) {
6524         if (!strncmp(message, "tellics ", 8)) {
6525             SendToICS(message + 8);
6526             SendToICS("\n");
6527             return;
6528         }
6529         if (!strncmp(message, "tellicsnoalias ", 15)) {
6530             SendToICS(ics_prefix);
6531             SendToICS(message + 15);
6532             SendToICS("\n");
6533             return;
6534         }
6535         /* The following are for backward compatibility only */
6536         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6537             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6538             SendToICS(ics_prefix);
6539             SendToICS(message);
6540             SendToICS("\n");
6541             return;
6542         }
6543     }
6544     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6545         return;
6546     }
6547     /*
6548      * If the move is illegal, cancel it and redraw the board.
6549      * Also deal with other error cases.  Matching is rather loose
6550      * here to accommodate engines written before the spec.
6551      */
6552     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6553         strncmp(message, "Error", 5) == 0) {
6554         if (StrStr(message, "name") || 
6555             StrStr(message, "rating") || StrStr(message, "?") ||
6556             StrStr(message, "result") || StrStr(message, "board") ||
6557             StrStr(message, "bk") || StrStr(message, "computer") ||
6558             StrStr(message, "variant") || StrStr(message, "hint") ||
6559             StrStr(message, "random") || StrStr(message, "depth") ||
6560             StrStr(message, "accepted")) {
6561             return;
6562         }
6563         if (StrStr(message, "protover")) {
6564           /* Program is responding to input, so it's apparently done
6565              initializing, and this error message indicates it is
6566              protocol version 1.  So we don't need to wait any longer
6567              for it to initialize and send feature commands. */
6568           FeatureDone(cps, 1);
6569           cps->protocolVersion = 1;
6570           return;
6571         }
6572         cps->maybeThinking = FALSE;
6573
6574         if (StrStr(message, "draw")) {
6575             /* Program doesn't have "draw" command */
6576             cps->sendDrawOffers = 0;
6577             return;
6578         }
6579         if (cps->sendTime != 1 &&
6580             (StrStr(message, "time") || StrStr(message, "otim"))) {
6581           /* Program apparently doesn't have "time" or "otim" command */
6582           cps->sendTime = 0;
6583           return;
6584         }
6585         if (StrStr(message, "analyze")) {
6586             cps->analysisSupport = FALSE;
6587             cps->analyzing = FALSE;
6588             Reset(FALSE, TRUE);
6589             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6590             DisplayError(buf2, 0);
6591             return;
6592         }
6593         if (StrStr(message, "(no matching move)st")) {
6594           /* Special kludge for GNU Chess 4 only */
6595           cps->stKludge = TRUE;
6596           SendTimeControl(cps, movesPerSession, timeControl,
6597                           timeIncrement, appData.searchDepth,
6598                           searchTime);
6599           return;
6600         }
6601         if (StrStr(message, "(no matching move)sd")) {
6602           /* Special kludge for GNU Chess 4 only */
6603           cps->sdKludge = TRUE;
6604           SendTimeControl(cps, movesPerSession, timeControl,
6605                           timeIncrement, appData.searchDepth,
6606                           searchTime);
6607           return;
6608         }
6609         if (!StrStr(message, "llegal")) {
6610             return;
6611         }
6612         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6613             gameMode == IcsIdle) return;
6614         if (forwardMostMove <= backwardMostMove) return;
6615         if (pausing) PauseEvent();
6616       if(appData.forceIllegal) {
6617             // [HGM] illegal: machine refused move; force position after move into it
6618           SendToProgram("force\n", cps);
6619           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6620                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6621                 // when black is to move, while there might be nothing on a2 or black
6622                 // might already have the move. So send the board as if white has the move.
6623                 // But first we must change the stm of the engine, as it refused the last move
6624                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6625                 if(WhiteOnMove(forwardMostMove)) {
6626                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6627                     SendBoard(cps, forwardMostMove); // kludgeless board
6628                 } else {
6629                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6630                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6631                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6632                 }
6633           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6634             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6635                  gameMode == TwoMachinesPlay)
6636               SendToProgram("go\n", cps);
6637             return;
6638       } else
6639         if (gameMode == PlayFromGameFile) {
6640             /* Stop reading this game file */
6641             gameMode = EditGame;
6642             ModeHighlight();
6643         }
6644         currentMove = --forwardMostMove;
6645         DisplayMove(currentMove-1); /* before DisplayMoveError */
6646         SwitchClocks();
6647         DisplayBothClocks();
6648         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6649                 parseList[currentMove], cps->which);
6650         DisplayMoveError(buf1);
6651         DrawPosition(FALSE, boards[currentMove]);
6652
6653         /* [HGM] illegal-move claim should forfeit game when Xboard */
6654         /* only passes fully legal moves                            */
6655         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6656             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6657                                 "False illegal-move claim", GE_XBOARD );
6658         }
6659         return;
6660     }
6661     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6662         /* Program has a broken "time" command that
6663            outputs a string not ending in newline.
6664            Don't use it. */
6665         cps->sendTime = 0;
6666     }
6667     
6668     /*
6669      * If chess program startup fails, exit with an error message.
6670      * Attempts to recover here are futile.
6671      */
6672     if ((StrStr(message, "unknown host") != NULL)
6673         || (StrStr(message, "No remote directory") != NULL)
6674         || (StrStr(message, "not found") != NULL)
6675         || (StrStr(message, "No such file") != NULL)
6676         || (StrStr(message, "can't alloc") != NULL)
6677         || (StrStr(message, "Permission denied") != NULL)) {
6678
6679         cps->maybeThinking = FALSE;
6680         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6681                 cps->which, cps->program, cps->host, message);
6682         RemoveInputSource(cps->isr);
6683         DisplayFatalError(buf1, 0, 1);
6684         return;
6685     }
6686     
6687     /* 
6688      * Look for hint output
6689      */
6690     if (sscanf(message, "Hint: %s", buf1) == 1) {
6691         if (cps == &first && hintRequested) {
6692             hintRequested = FALSE;
6693             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6694                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6695                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6696                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6697                                     fromY, fromX, toY, toX, promoChar, buf1);
6698                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6699                 DisplayInformation(buf2);
6700             } else {
6701                 /* Hint move could not be parsed!? */
6702               snprintf(buf2, sizeof(buf2),
6703                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6704                         buf1, cps->which);
6705                 DisplayError(buf2, 0);
6706             }
6707         } else {
6708             strcpy(lastHint, buf1);
6709         }
6710         return;
6711     }
6712
6713     /*
6714      * Ignore other messages if game is not in progress
6715      */
6716     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6717         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6718
6719     /*
6720      * look for win, lose, draw, or draw offer
6721      */
6722     if (strncmp(message, "1-0", 3) == 0) {
6723         char *p, *q, *r = "";
6724         p = strchr(message, '{');
6725         if (p) {
6726             q = strchr(p, '}');
6727             if (q) {
6728                 *q = NULLCHAR;
6729                 r = p + 1;
6730             }
6731         }
6732         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6733         return;
6734     } else if (strncmp(message, "0-1", 3) == 0) {
6735         char *p, *q, *r = "";
6736         p = strchr(message, '{');
6737         if (p) {
6738             q = strchr(p, '}');
6739             if (q) {
6740                 *q = NULLCHAR;
6741                 r = p + 1;
6742             }
6743         }
6744         /* Kludge for Arasan 4.1 bug */
6745         if (strcmp(r, "Black resigns") == 0) {
6746             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6747             return;
6748         }
6749         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6750         return;
6751     } else if (strncmp(message, "1/2", 3) == 0) {
6752         char *p, *q, *r = "";
6753         p = strchr(message, '{');
6754         if (p) {
6755             q = strchr(p, '}');
6756             if (q) {
6757                 *q = NULLCHAR;
6758                 r = p + 1;
6759             }
6760         }
6761             
6762         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6763         return;
6764
6765     } else if (strncmp(message, "White resign", 12) == 0) {
6766         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6767         return;
6768     } else if (strncmp(message, "Black resign", 12) == 0) {
6769         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6770         return;
6771     } else if (strncmp(message, "White matches", 13) == 0 ||
6772                strncmp(message, "Black matches", 13) == 0   ) {
6773         /* [HGM] ignore GNUShogi noises */
6774         return;
6775     } else if (strncmp(message, "White", 5) == 0 &&
6776                message[5] != '(' &&
6777                StrStr(message, "Black") == NULL) {
6778         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6779         return;
6780     } else if (strncmp(message, "Black", 5) == 0 &&
6781                message[5] != '(') {
6782         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6783         return;
6784     } else if (strcmp(message, "resign") == 0 ||
6785                strcmp(message, "computer resigns") == 0) {
6786         switch (gameMode) {
6787           case MachinePlaysBlack:
6788           case IcsPlayingBlack:
6789             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6790             break;
6791           case MachinePlaysWhite:
6792           case IcsPlayingWhite:
6793             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6794             break;
6795           case TwoMachinesPlay:
6796             if (cps->twoMachinesColor[0] == 'w')
6797               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6798             else
6799               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6800             break;
6801           default:
6802             /* can't happen */
6803             break;
6804         }
6805         return;
6806     } else if (strncmp(message, "opponent mates", 14) == 0) {
6807         switch (gameMode) {
6808           case MachinePlaysBlack:
6809           case IcsPlayingBlack:
6810             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6811             break;
6812           case MachinePlaysWhite:
6813           case IcsPlayingWhite:
6814             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6815             break;
6816           case TwoMachinesPlay:
6817             if (cps->twoMachinesColor[0] == 'w')
6818               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6819             else
6820               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6821             break;
6822           default:
6823             /* can't happen */
6824             break;
6825         }
6826         return;
6827     } else if (strncmp(message, "computer mates", 14) == 0) {
6828         switch (gameMode) {
6829           case MachinePlaysBlack:
6830           case IcsPlayingBlack:
6831             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6832             break;
6833           case MachinePlaysWhite:
6834           case IcsPlayingWhite:
6835             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6836             break;
6837           case TwoMachinesPlay:
6838             if (cps->twoMachinesColor[0] == 'w')
6839               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6840             else
6841               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6842             break;
6843           default:
6844             /* can't happen */
6845             break;
6846         }
6847         return;
6848     } else if (strncmp(message, "checkmate", 9) == 0) {
6849         if (WhiteOnMove(forwardMostMove)) {
6850             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6851         } else {
6852             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6853         }
6854         return;
6855     } else if (strstr(message, "Draw") != NULL ||
6856                strstr(message, "game is a draw") != NULL) {
6857         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6858         return;
6859     } else if (strstr(message, "offer") != NULL &&
6860                strstr(message, "draw") != NULL) {
6861 #if ZIPPY
6862         if (appData.zippyPlay && first.initDone) {
6863             /* Relay offer to ICS */
6864             SendToICS(ics_prefix);
6865             SendToICS("draw\n");
6866         }
6867 #endif
6868         cps->offeredDraw = 2; /* valid until this engine moves twice */
6869         if (gameMode == TwoMachinesPlay) {
6870             if (cps->other->offeredDraw) {
6871                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6872             /* [HGM] in two-machine mode we delay relaying draw offer      */
6873             /* until after we also have move, to see if it is really claim */
6874             }
6875         } else if (gameMode == MachinePlaysWhite ||
6876                    gameMode == MachinePlaysBlack) {
6877           if (userOfferedDraw) {
6878             DisplayInformation(_("Machine accepts your draw offer"));
6879             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6880           } else {
6881             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6882           }
6883         }
6884     }
6885
6886     
6887     /*
6888      * Look for thinking output
6889      */
6890     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6891           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6892                                 ) {
6893         int plylev, mvleft, mvtot, curscore, time;
6894         char mvname[MOVE_LEN];
6895         u64 nodes; // [DM]
6896         char plyext;
6897         int ignore = FALSE;
6898         int prefixHint = FALSE;
6899         mvname[0] = NULLCHAR;
6900
6901         switch (gameMode) {
6902           case MachinePlaysBlack:
6903           case IcsPlayingBlack:
6904             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6905             break;
6906           case MachinePlaysWhite:
6907           case IcsPlayingWhite:
6908             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6909             break;
6910           case AnalyzeMode:
6911           case AnalyzeFile:
6912             break;
6913           case IcsObserving: /* [DM] icsEngineAnalyze */
6914             if (!appData.icsEngineAnalyze) ignore = TRUE;
6915             break;
6916           case TwoMachinesPlay:
6917             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6918                 ignore = TRUE;
6919             }
6920             break;
6921           default:
6922             ignore = TRUE;
6923             break;
6924         }
6925
6926         if (!ignore) {
6927             buf1[0] = NULLCHAR;
6928             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6929                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6930
6931                 if (plyext != ' ' && plyext != '\t') {
6932                     time *= 100;
6933                 }
6934
6935                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6936                 if( cps->scoreIsAbsolute && 
6937                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6938                 {
6939                     curscore = -curscore;
6940                 }
6941
6942
6943                 programStats.depth = plylev;
6944                 programStats.nodes = nodes;
6945                 programStats.time = time;
6946                 programStats.score = curscore;
6947                 programStats.got_only_move = 0;
6948
6949                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6950                         int ticklen;
6951
6952                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6953                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6954                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6955                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6956                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6957                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6958                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6959                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6960                 }
6961
6962                 /* Buffer overflow protection */
6963                 if (buf1[0] != NULLCHAR) {
6964                     if (strlen(buf1) >= sizeof(programStats.movelist)
6965                         && appData.debugMode) {
6966                         fprintf(debugFP,
6967                                 "PV is too long; using the first %u bytes.\n",
6968                                 (unsigned) sizeof(programStats.movelist) - 1);
6969                     }
6970
6971                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6972                 } else {
6973                     sprintf(programStats.movelist, " no PV\n");
6974                 }
6975
6976                 if (programStats.seen_stat) {
6977                     programStats.ok_to_send = 1;
6978                 }
6979
6980                 if (strchr(programStats.movelist, '(') != NULL) {
6981                     programStats.line_is_book = 1;
6982                     programStats.nr_moves = 0;
6983                     programStats.moves_left = 0;
6984                 } else {
6985                     programStats.line_is_book = 0;
6986                 }
6987
6988                 SendProgramStatsToFrontend( cps, &programStats );
6989
6990                 /* 
6991                     [AS] Protect the thinkOutput buffer from overflow... this
6992                     is only useful if buf1 hasn't overflowed first!
6993                 */
6994                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6995                         plylev, 
6996                         (gameMode == TwoMachinesPlay ?
6997                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6998                         ((double) curscore) / 100.0,
6999                         prefixHint ? lastHint : "",
7000                         prefixHint ? " " : "" );
7001
7002                 if( buf1[0] != NULLCHAR ) {
7003                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7004
7005                     if( strlen(buf1) > max_len ) {
7006                         if( appData.debugMode) {
7007                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7008                         }
7009                         buf1[max_len+1] = '\0';
7010                     }
7011
7012                     strcat( thinkOutput, buf1 );
7013                 }
7014
7015                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7016                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7017                     DisplayMove(currentMove - 1);
7018                 }
7019                 return;
7020
7021             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7022                 /* crafty (9.25+) says "(only move) <move>"
7023                  * if there is only 1 legal move
7024                  */
7025                 sscanf(p, "(only move) %s", buf1);
7026                 sprintf(thinkOutput, "%s (only move)", buf1);
7027                 sprintf(programStats.movelist, "%s (only move)", buf1);
7028                 programStats.depth = 1;
7029                 programStats.nr_moves = 1;
7030                 programStats.moves_left = 1;
7031                 programStats.nodes = 1;
7032                 programStats.time = 1;
7033                 programStats.got_only_move = 1;
7034
7035                 /* Not really, but we also use this member to
7036                    mean "line isn't going to change" (Crafty
7037                    isn't searching, so stats won't change) */
7038                 programStats.line_is_book = 1;
7039
7040                 SendProgramStatsToFrontend( cps, &programStats );
7041                 
7042                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7043                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7044                     DisplayMove(currentMove - 1);
7045                 }
7046                 return;
7047             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7048                               &time, &nodes, &plylev, &mvleft,
7049                               &mvtot, mvname) >= 5) {
7050                 /* The stat01: line is from Crafty (9.29+) in response
7051                    to the "." command */
7052                 programStats.seen_stat = 1;
7053                 cps->maybeThinking = TRUE;
7054
7055                 if (programStats.got_only_move || !appData.periodicUpdates)
7056                   return;
7057
7058                 programStats.depth = plylev;
7059                 programStats.time = time;
7060                 programStats.nodes = nodes;
7061                 programStats.moves_left = mvleft;
7062                 programStats.nr_moves = mvtot;
7063                 strcpy(programStats.move_name, mvname);
7064                 programStats.ok_to_send = 1;
7065                 programStats.movelist[0] = '\0';
7066
7067                 SendProgramStatsToFrontend( cps, &programStats );
7068
7069                 return;
7070
7071             } else if (strncmp(message,"++",2) == 0) {
7072                 /* Crafty 9.29+ outputs this */
7073                 programStats.got_fail = 2;
7074                 return;
7075
7076             } else if (strncmp(message,"--",2) == 0) {
7077                 /* Crafty 9.29+ outputs this */
7078                 programStats.got_fail = 1;
7079                 return;
7080
7081             } else if (thinkOutput[0] != NULLCHAR &&
7082                        strncmp(message, "    ", 4) == 0) {
7083                 unsigned message_len;
7084
7085                 p = message;
7086                 while (*p && *p == ' ') p++;
7087
7088                 message_len = strlen( p );
7089
7090                 /* [AS] Avoid buffer overflow */
7091                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7092                     strcat(thinkOutput, " ");
7093                     strcat(thinkOutput, p);
7094                 }
7095
7096                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7097                     strcat(programStats.movelist, " ");
7098                     strcat(programStats.movelist, p);
7099                 }
7100
7101                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7102                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7103                     DisplayMove(currentMove - 1);
7104                 }
7105                 return;
7106             }
7107         }
7108         else {
7109             buf1[0] = NULLCHAR;
7110
7111             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7112                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7113             {
7114                 ChessProgramStats cpstats;
7115
7116                 if (plyext != ' ' && plyext != '\t') {
7117                     time *= 100;
7118                 }
7119
7120                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7121                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7122                     curscore = -curscore;
7123                 }
7124
7125                 cpstats.depth = plylev;
7126                 cpstats.nodes = nodes;
7127                 cpstats.time = time;
7128                 cpstats.score = curscore;
7129                 cpstats.got_only_move = 0;
7130                 cpstats.movelist[0] = '\0';
7131
7132                 if (buf1[0] != NULLCHAR) {
7133                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7134                 }
7135
7136                 cpstats.ok_to_send = 0;
7137                 cpstats.line_is_book = 0;
7138                 cpstats.nr_moves = 0;
7139                 cpstats.moves_left = 0;
7140
7141                 SendProgramStatsToFrontend( cps, &cpstats );
7142             }
7143         }
7144     }
7145 }
7146
7147
7148 /* Parse a game score from the character string "game", and
7149    record it as the history of the current game.  The game
7150    score is NOT assumed to start from the standard position. 
7151    The display is not updated in any way.
7152    */
7153 void
7154 ParseGameHistory(game)
7155      char *game;
7156 {
7157     ChessMove moveType;
7158     int fromX, fromY, toX, toY, boardIndex;
7159     char promoChar;
7160     char *p, *q;
7161     char buf[MSG_SIZ];
7162
7163     if (appData.debugMode)
7164       fprintf(debugFP, "Parsing game history: %s\n", game);
7165
7166     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7167     gameInfo.site = StrSave(appData.icsHost);
7168     gameInfo.date = PGNDate();
7169     gameInfo.round = StrSave("-");
7170
7171     /* Parse out names of players */
7172     while (*game == ' ') game++;
7173     p = buf;
7174     while (*game != ' ') *p++ = *game++;
7175     *p = NULLCHAR;
7176     gameInfo.white = StrSave(buf);
7177     while (*game == ' ') game++;
7178     p = buf;
7179     while (*game != ' ' && *game != '\n') *p++ = *game++;
7180     *p = NULLCHAR;
7181     gameInfo.black = StrSave(buf);
7182
7183     /* Parse moves */
7184     boardIndex = blackPlaysFirst ? 1 : 0;
7185     yynewstr(game);
7186     for (;;) {
7187         yyboardindex = boardIndex;
7188         moveType = (ChessMove) yylex();
7189         switch (moveType) {
7190           case IllegalMove:             /* maybe suicide chess, etc. */
7191   if (appData.debugMode) {
7192     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7193     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7194     setbuf(debugFP, NULL);
7195   }
7196           case WhitePromotionChancellor:
7197           case BlackPromotionChancellor:
7198           case WhitePromotionArchbishop:
7199           case BlackPromotionArchbishop:
7200           case WhitePromotionQueen:
7201           case BlackPromotionQueen:
7202           case WhitePromotionRook:
7203           case BlackPromotionRook:
7204           case WhitePromotionBishop:
7205           case BlackPromotionBishop:
7206           case WhitePromotionKnight:
7207           case BlackPromotionKnight:
7208           case WhitePromotionKing:
7209           case BlackPromotionKing:
7210           case NormalMove:
7211           case WhiteCapturesEnPassant:
7212           case BlackCapturesEnPassant:
7213           case WhiteKingSideCastle:
7214           case WhiteQueenSideCastle:
7215           case BlackKingSideCastle:
7216           case BlackQueenSideCastle:
7217           case WhiteKingSideCastleWild:
7218           case WhiteQueenSideCastleWild:
7219           case BlackKingSideCastleWild:
7220           case BlackQueenSideCastleWild:
7221           /* PUSH Fabien */
7222           case WhiteHSideCastleFR:
7223           case WhiteASideCastleFR:
7224           case BlackHSideCastleFR:
7225           case BlackASideCastleFR:
7226           /* POP Fabien */
7227             fromX = currentMoveString[0] - AAA;
7228             fromY = currentMoveString[1] - ONE;
7229             toX = currentMoveString[2] - AAA;
7230             toY = currentMoveString[3] - ONE;
7231             promoChar = currentMoveString[4];
7232             break;
7233           case WhiteDrop:
7234           case BlackDrop:
7235             fromX = moveType == WhiteDrop ?
7236               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7237             (int) CharToPiece(ToLower(currentMoveString[0]));
7238             fromY = DROP_RANK;
7239             toX = currentMoveString[2] - AAA;
7240             toY = currentMoveString[3] - ONE;
7241             promoChar = NULLCHAR;
7242             break;
7243           case AmbiguousMove:
7244             /* bug? */
7245             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7246   if (appData.debugMode) {
7247     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7248     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7249     setbuf(debugFP, NULL);
7250   }
7251             DisplayError(buf, 0);
7252             return;
7253           case ImpossibleMove:
7254             /* bug? */
7255             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7256   if (appData.debugMode) {
7257     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7259     setbuf(debugFP, NULL);
7260   }
7261             DisplayError(buf, 0);
7262             return;
7263           case (ChessMove) 0:   /* end of file */
7264             if (boardIndex < backwardMostMove) {
7265                 /* Oops, gap.  How did that happen? */
7266                 DisplayError(_("Gap in move list"), 0);
7267                 return;
7268             }
7269             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7270             if (boardIndex > forwardMostMove) {
7271                 forwardMostMove = boardIndex;
7272             }
7273             return;
7274           case ElapsedTime:
7275             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7276                 strcat(parseList[boardIndex-1], " ");
7277                 strcat(parseList[boardIndex-1], yy_text);
7278             }
7279             continue;
7280           case Comment:
7281           case PGNTag:
7282           case NAG:
7283           default:
7284             /* ignore */
7285             continue;
7286           case WhiteWins:
7287           case BlackWins:
7288           case GameIsDrawn:
7289           case GameUnfinished:
7290             if (gameMode == IcsExamining) {
7291                 if (boardIndex < backwardMostMove) {
7292                     /* Oops, gap.  How did that happen? */
7293                     return;
7294                 }
7295                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7296                 return;
7297             }
7298             gameInfo.result = moveType;
7299             p = strchr(yy_text, '{');
7300             if (p == NULL) p = strchr(yy_text, '(');
7301             if (p == NULL) {
7302                 p = yy_text;
7303                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7304             } else {
7305                 q = strchr(p, *p == '{' ? '}' : ')');
7306                 if (q != NULL) *q = NULLCHAR;
7307                 p++;
7308             }
7309             gameInfo.resultDetails = StrSave(p);
7310             continue;
7311         }
7312         if (boardIndex >= forwardMostMove &&
7313             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7314             backwardMostMove = blackPlaysFirst ? 1 : 0;
7315             return;
7316         }
7317         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7318                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7319                                  parseList[boardIndex]);
7320         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7321         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7322         /* currentMoveString is set as a side-effect of yylex */
7323         strcpy(moveList[boardIndex], currentMoveString);
7324         strcat(moveList[boardIndex], "\n");
7325         boardIndex++;
7326         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7327                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7328         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7329                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7330           case MT_NONE:
7331           case MT_STALEMATE:
7332           default:
7333             break;
7334           case MT_CHECK:
7335             if(gameInfo.variant != VariantShogi)
7336                 strcat(parseList[boardIndex - 1], "+");
7337             break;
7338           case MT_CHECKMATE:
7339           case MT_STAINMATE:
7340             strcat(parseList[boardIndex - 1], "#");
7341             break;
7342         }
7343     }
7344 }
7345
7346
7347 /* Apply a move to the given board  */
7348 void
7349 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7350      int fromX, fromY, toX, toY;
7351      int promoChar;
7352      Board board;
7353      char *castling;
7354      char *ep;
7355 {
7356   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7357
7358     /* [HGM] compute & store e.p. status and castling rights for new position */
7359     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7360     { int i;
7361
7362       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7363       oldEP = *ep;
7364       *ep = EP_NONE;
7365
7366       if( board[toY][toX] != EmptySquare ) 
7367            *ep = EP_CAPTURE;  
7368
7369       if( board[fromY][fromX] == WhitePawn ) {
7370            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7371                *ep = EP_PAWN_MOVE;
7372            if( toY-fromY==2) {
7373                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7374                         gameInfo.variant != VariantBerolina || toX < fromX)
7375                       *ep = toX | berolina;
7376                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7377                         gameInfo.variant != VariantBerolina || toX > fromX) 
7378                       *ep = toX;
7379            }
7380       } else 
7381       if( board[fromY][fromX] == BlackPawn ) {
7382            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7383                *ep = EP_PAWN_MOVE; 
7384            if( toY-fromY== -2) {
7385                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7386                         gameInfo.variant != VariantBerolina || toX < fromX)
7387                       *ep = toX | berolina;
7388                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7389                         gameInfo.variant != VariantBerolina || toX > fromX) 
7390                       *ep = toX;
7391            }
7392        }
7393
7394        for(i=0; i<nrCastlingRights; i++) {
7395            if(castling[i] == fromX && castlingRank[i] == fromY ||
7396               castling[i] == toX   && castlingRank[i] == toY   
7397              ) castling[i] = -1; // revoke for moved or captured piece
7398        }
7399
7400     }
7401
7402   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7403   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7404        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7405          
7406   if (fromX == toX && fromY == toY) return;
7407
7408   if (fromY == DROP_RANK) {
7409         /* must be first */
7410         piece = board[toY][toX] = (ChessSquare) fromX;
7411   } else {
7412      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7413      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7414      if(gameInfo.variant == VariantKnightmate)
7415          king += (int) WhiteUnicorn - (int) WhiteKing;
7416
7417     /* Code added by Tord: */
7418     /* FRC castling assumed when king captures friendly rook. */
7419     if (board[fromY][fromX] == WhiteKing &&
7420              board[toY][toX] == WhiteRook) {
7421       board[fromY][fromX] = EmptySquare;
7422       board[toY][toX] = EmptySquare;
7423       if(toX > fromX) {
7424         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7425       } else {
7426         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7427       }
7428     } else if (board[fromY][fromX] == BlackKing &&
7429                board[toY][toX] == BlackRook) {
7430       board[fromY][fromX] = EmptySquare;
7431       board[toY][toX] = EmptySquare;
7432       if(toX > fromX) {
7433         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7434       } else {
7435         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7436       }
7437     /* End of code added by Tord */
7438
7439     } else if (board[fromY][fromX] == king
7440         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7441         && toY == fromY && toX > fromX+1) {
7442         board[fromY][fromX] = EmptySquare;
7443         board[toY][toX] = king;
7444         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7445         board[fromY][BOARD_RGHT-1] = EmptySquare;
7446     } else if (board[fromY][fromX] == king
7447         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7448                && toY == fromY && toX < fromX-1) {
7449         board[fromY][fromX] = EmptySquare;
7450         board[toY][toX] = king;
7451         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7452         board[fromY][BOARD_LEFT] = EmptySquare;
7453     } else if (board[fromY][fromX] == WhitePawn
7454                && toY == BOARD_HEIGHT-1
7455                && gameInfo.variant != VariantXiangqi
7456                ) {
7457         /* white pawn promotion */
7458         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7459         if (board[toY][toX] == EmptySquare) {
7460             board[toY][toX] = WhiteQueen;
7461         }
7462         if(gameInfo.variant==VariantBughouse ||
7463            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7464             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7465         board[fromY][fromX] = EmptySquare;
7466     } else if ((fromY == BOARD_HEIGHT-4)
7467                && (toX != fromX)
7468                && gameInfo.variant != VariantXiangqi
7469                && gameInfo.variant != VariantBerolina
7470                && (board[fromY][fromX] == WhitePawn)
7471                && (board[toY][toX] == EmptySquare)) {
7472         board[fromY][fromX] = EmptySquare;
7473         board[toY][toX] = WhitePawn;
7474         captured = board[toY - 1][toX];
7475         board[toY - 1][toX] = EmptySquare;
7476     } else if ((fromY == BOARD_HEIGHT-4)
7477                && (toX == fromX)
7478                && gameInfo.variant == VariantBerolina
7479                && (board[fromY][fromX] == WhitePawn)
7480                && (board[toY][toX] == EmptySquare)) {
7481         board[fromY][fromX] = EmptySquare;
7482         board[toY][toX] = WhitePawn;
7483         if(oldEP & EP_BEROLIN_A) {
7484                 captured = board[fromY][fromX-1];
7485                 board[fromY][fromX-1] = EmptySquare;
7486         }else{  captured = board[fromY][fromX+1];
7487                 board[fromY][fromX+1] = EmptySquare;
7488         }
7489     } else if (board[fromY][fromX] == king
7490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7491                && toY == fromY && toX > fromX+1) {
7492         board[fromY][fromX] = EmptySquare;
7493         board[toY][toX] = king;
7494         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7495         board[fromY][BOARD_RGHT-1] = EmptySquare;
7496     } else if (board[fromY][fromX] == king
7497         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7498                && toY == fromY && toX < fromX-1) {
7499         board[fromY][fromX] = EmptySquare;
7500         board[toY][toX] = king;
7501         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7502         board[fromY][BOARD_LEFT] = EmptySquare;
7503     } else if (fromY == 7 && fromX == 3
7504                && board[fromY][fromX] == BlackKing
7505                && toY == 7 && toX == 5) {
7506         board[fromY][fromX] = EmptySquare;
7507         board[toY][toX] = BlackKing;
7508         board[fromY][7] = EmptySquare;
7509         board[toY][4] = BlackRook;
7510     } else if (fromY == 7 && fromX == 3
7511                && board[fromY][fromX] == BlackKing
7512                && toY == 7 && toX == 1) {
7513         board[fromY][fromX] = EmptySquare;
7514         board[toY][toX] = BlackKing;
7515         board[fromY][0] = EmptySquare;
7516         board[toY][2] = BlackRook;
7517     } else if (board[fromY][fromX] == BlackPawn
7518                && toY == 0
7519                && gameInfo.variant != VariantXiangqi
7520                ) {
7521         /* black pawn promotion */
7522         board[0][toX] = CharToPiece(ToLower(promoChar));
7523         if (board[0][toX] == EmptySquare) {
7524             board[0][toX] = BlackQueen;
7525         }
7526         if(gameInfo.variant==VariantBughouse ||
7527            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7528             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7529         board[fromY][fromX] = EmptySquare;
7530     } else if ((fromY == 3)
7531                && (toX != fromX)
7532                && gameInfo.variant != VariantXiangqi
7533                && gameInfo.variant != VariantBerolina
7534                && (board[fromY][fromX] == BlackPawn)
7535                && (board[toY][toX] == EmptySquare)) {
7536         board[fromY][fromX] = EmptySquare;
7537         board[toY][toX] = BlackPawn;
7538         captured = board[toY + 1][toX];
7539         board[toY + 1][toX] = EmptySquare;
7540     } else if ((fromY == 3)
7541                && (toX == fromX)
7542                && gameInfo.variant == VariantBerolina
7543                && (board[fromY][fromX] == BlackPawn)
7544                && (board[toY][toX] == EmptySquare)) {
7545         board[fromY][fromX] = EmptySquare;
7546         board[toY][toX] = BlackPawn;
7547         if(oldEP & EP_BEROLIN_A) {
7548                 captured = board[fromY][fromX-1];
7549                 board[fromY][fromX-1] = EmptySquare;
7550         }else{  captured = board[fromY][fromX+1];
7551                 board[fromY][fromX+1] = EmptySquare;
7552         }
7553     } else {
7554         board[toY][toX] = board[fromY][fromX];
7555         board[fromY][fromX] = EmptySquare;
7556     }
7557
7558     /* [HGM] now we promote for Shogi, if needed */
7559     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7560         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7561   }
7562
7563     if (gameInfo.holdingsWidth != 0) {
7564
7565       /* !!A lot more code needs to be written to support holdings  */
7566       /* [HGM] OK, so I have written it. Holdings are stored in the */
7567       /* penultimate board files, so they are automaticlly stored   */
7568       /* in the game history.                                       */
7569       if (fromY == DROP_RANK) {
7570         /* Delete from holdings, by decreasing count */
7571         /* and erasing image if necessary            */
7572         p = (int) fromX;
7573         if(p < (int) BlackPawn) { /* white drop */
7574              p -= (int)WhitePawn;
7575                  p = PieceToNumber((ChessSquare)p);
7576              if(p >= gameInfo.holdingsSize) p = 0;
7577              if(--board[p][BOARD_WIDTH-2] <= 0)
7578                   board[p][BOARD_WIDTH-1] = EmptySquare;
7579              if((int)board[p][BOARD_WIDTH-2] < 0)
7580                         board[p][BOARD_WIDTH-2] = 0;
7581         } else {                  /* black drop */
7582              p -= (int)BlackPawn;
7583                  p = PieceToNumber((ChessSquare)p);
7584              if(p >= gameInfo.holdingsSize) p = 0;
7585              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7586                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7587              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7588                         board[BOARD_HEIGHT-1-p][1] = 0;
7589         }
7590       }
7591       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7592           && gameInfo.variant != VariantBughouse        ) {
7593         /* [HGM] holdings: Add to holdings, if holdings exist */
7594         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7595                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7596                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7597         }
7598         p = (int) captured;
7599         if (p >= (int) BlackPawn) {
7600           p -= (int)BlackPawn;
7601           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7602                   /* in Shogi restore piece to its original  first */
7603                   captured = (ChessSquare) (DEMOTED captured);
7604                   p = DEMOTED p;
7605           }
7606           p = PieceToNumber((ChessSquare)p);
7607           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7608           board[p][BOARD_WIDTH-2]++;
7609           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7610         } else {
7611           p -= (int)WhitePawn;
7612           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7613                   captured = (ChessSquare) (DEMOTED captured);
7614                   p = DEMOTED p;
7615           }
7616           p = PieceToNumber((ChessSquare)p);
7617           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7618           board[BOARD_HEIGHT-1-p][1]++;
7619           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7620         }
7621       }
7622     } else if (gameInfo.variant == VariantAtomic) {
7623       if (captured != EmptySquare) {
7624         int y, x;
7625         for (y = toY-1; y <= toY+1; y++) {
7626           for (x = toX-1; x <= toX+1; x++) {
7627             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7628                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7629               board[y][x] = EmptySquare;
7630             }
7631           }
7632         }
7633         board[toY][toX] = EmptySquare;
7634       }
7635     }
7636     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7637         /* [HGM] Shogi promotions */
7638         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7639     }
7640
7641     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7642                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7643         // [HGM] superchess: take promotion piece out of holdings
7644         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7645         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7646             if(!--board[k][BOARD_WIDTH-2])
7647                 board[k][BOARD_WIDTH-1] = EmptySquare;
7648         } else {
7649             if(!--board[BOARD_HEIGHT-1-k][1])
7650                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7651         }
7652     }
7653
7654 }
7655
7656 /* Updates forwardMostMove */
7657 void
7658 MakeMove(fromX, fromY, toX, toY, promoChar)
7659      int fromX, fromY, toX, toY;
7660      int promoChar;
7661 {
7662 //    forwardMostMove++; // [HGM] bare: moved downstream
7663
7664     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7665         int timeLeft; static int lastLoadFlag=0; int king, piece;
7666         piece = boards[forwardMostMove][fromY][fromX];
7667         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7668         if(gameInfo.variant == VariantKnightmate)
7669             king += (int) WhiteUnicorn - (int) WhiteKing;
7670         if(forwardMostMove == 0) {
7671             if(blackPlaysFirst) 
7672                 fprintf(serverMoves, "%s;", second.tidy);
7673             fprintf(serverMoves, "%s;", first.tidy);
7674             if(!blackPlaysFirst) 
7675                 fprintf(serverMoves, "%s;", second.tidy);
7676         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7677         lastLoadFlag = loadFlag;
7678         // print base move
7679         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7680         // print castling suffix
7681         if( toY == fromY && piece == king ) {
7682             if(toX-fromX > 1)
7683                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7684             if(fromX-toX >1)
7685                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7686         }
7687         // e.p. suffix
7688         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7689              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7690              boards[forwardMostMove][toY][toX] == EmptySquare
7691              && fromX != toX )
7692                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7693         // promotion suffix
7694         if(promoChar != NULLCHAR)
7695                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7696         if(!loadFlag) {
7697             fprintf(serverMoves, "/%d/%d",
7698                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7699             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7700             else                      timeLeft = blackTimeRemaining/1000;
7701             fprintf(serverMoves, "/%d", timeLeft);
7702         }
7703         fflush(serverMoves);
7704     }
7705
7706     if (forwardMostMove+1 >= MAX_MOVES) {
7707       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7708                         0, 1);
7709       return;
7710     }
7711     if (commentList[forwardMostMove+1] != NULL) {
7712         free(commentList[forwardMostMove+1]);
7713         commentList[forwardMostMove+1] = NULL;
7714     }
7715     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7716     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7717     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7718                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7719     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7720     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7721     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7722     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7723     gameInfo.result = GameUnfinished;
7724     if (gameInfo.resultDetails != NULL) {
7725         free(gameInfo.resultDetails);
7726         gameInfo.resultDetails = NULL;
7727     }
7728     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7729                               moveList[forwardMostMove - 1]);
7730     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7731                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7732                              fromY, fromX, toY, toX, promoChar,
7733                              parseList[forwardMostMove - 1]);
7734     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7735                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7736                             castlingRights[forwardMostMove]) ) {
7737       case MT_NONE:
7738       case MT_STALEMATE:
7739       default:
7740         break;
7741       case MT_CHECK:
7742         if(gameInfo.variant != VariantShogi)
7743             strcat(parseList[forwardMostMove - 1], "+");
7744         break;
7745       case MT_CHECKMATE:
7746       case MT_STAINMATE:
7747         strcat(parseList[forwardMostMove - 1], "#");
7748         break;
7749     }
7750     if (appData.debugMode) {
7751         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7752     }
7753
7754 }
7755
7756 /* Updates currentMove if not pausing */
7757 void
7758 ShowMove(fromX, fromY, toX, toY)
7759 {
7760     int instant = (gameMode == PlayFromGameFile) ?
7761         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7762     if(appData.noGUI) return;
7763     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7764         if (!instant) {
7765             if (forwardMostMove == currentMove + 1) {
7766                 AnimateMove(boards[forwardMostMove - 1],
7767                             fromX, fromY, toX, toY);
7768             }
7769             if (appData.highlightLastMove) {
7770                 SetHighlights(fromX, fromY, toX, toY);
7771             }
7772         }
7773         currentMove = forwardMostMove;
7774     }
7775
7776     if (instant) return;
7777
7778     DisplayMove(currentMove - 1);
7779     DrawPosition(FALSE, boards[currentMove]);
7780     DisplayBothClocks();
7781     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7782 }
7783
7784 void SendEgtPath(ChessProgramState *cps)
7785 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7786         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7787
7788         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7789
7790         while(*p) {
7791             char c, *q = name+1, *r, *s;
7792
7793             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7794             while(*p && *p != ',') *q++ = *p++;
7795             *q++ = ':'; *q = 0;
7796             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7797                 strcmp(name, ",nalimov:") == 0 ) {
7798                 // take nalimov path from the menu-changeable option first, if it is defined
7799                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7800                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7801             } else
7802             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7803                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7804                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7805                 s = r = StrStr(s, ":") + 1; // beginning of path info
7806                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7807                 c = *r; *r = 0;             // temporarily null-terminate path info
7808                     *--q = 0;               // strip of trailig ':' from name
7809                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7810                 *r = c;
7811                 SendToProgram(buf,cps);     // send egtbpath command for this format
7812             }
7813             if(*p == ',') p++; // read away comma to position for next format name
7814         }
7815 }
7816
7817 void
7818 InitChessProgram(cps, setup)
7819      ChessProgramState *cps;
7820      int setup; /* [HGM] needed to setup FRC opening position */
7821 {
7822     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7823     if (appData.noChessProgram) return;
7824     hintRequested = FALSE;
7825     bookRequested = FALSE;
7826
7827     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7828     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7829     if(cps->memSize) { /* [HGM] memory */
7830         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7831         SendToProgram(buf, cps);
7832     }
7833     SendEgtPath(cps); /* [HGM] EGT */
7834     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7835         sprintf(buf, "cores %d\n", appData.smpCores);
7836         SendToProgram(buf, cps);
7837     }
7838
7839     SendToProgram(cps->initString, cps);
7840     if (gameInfo.variant != VariantNormal &&
7841         gameInfo.variant != VariantLoadable
7842         /* [HGM] also send variant if board size non-standard */
7843         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7844                                             ) {
7845       char *v = VariantName(gameInfo.variant);
7846       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7847         /* [HGM] in protocol 1 we have to assume all variants valid */
7848         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7849         DisplayFatalError(buf, 0, 1);
7850         return;
7851       }
7852
7853       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7854       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7855       if( gameInfo.variant == VariantXiangqi )
7856            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7857       if( gameInfo.variant == VariantShogi )
7858            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7859       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7860            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7861       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7862                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7863            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7864       if( gameInfo.variant == VariantCourier )
7865            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7866       if( gameInfo.variant == VariantSuper )
7867            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7868       if( gameInfo.variant == VariantGreat )
7869            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7870
7871       if(overruled) {
7872            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7873                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7874            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7875            if(StrStr(cps->variants, b) == NULL) { 
7876                // specific sized variant not known, check if general sizing allowed
7877                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7878                    if(StrStr(cps->variants, "boardsize") == NULL) {
7879                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7880                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7881                        DisplayFatalError(buf, 0, 1);
7882                        return;
7883                    }
7884                    /* [HGM] here we really should compare with the maximum supported board size */
7885                }
7886            }
7887       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7888       sprintf(buf, "variant %s\n", b);
7889       SendToProgram(buf, cps);
7890     }
7891     currentlyInitializedVariant = gameInfo.variant;
7892
7893     /* [HGM] send opening position in FRC to first engine */
7894     if(setup) {
7895           SendToProgram("force\n", cps);
7896           SendBoard(cps, 0);
7897           /* engine is now in force mode! Set flag to wake it up after first move. */
7898           setboardSpoiledMachineBlack = 1;
7899     }
7900
7901     if (cps->sendICS) {
7902       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7903       SendToProgram(buf, cps);
7904     }
7905     cps->maybeThinking = FALSE;
7906     cps->offeredDraw = 0;
7907     if (!appData.icsActive) {
7908         SendTimeControl(cps, movesPerSession, timeControl,
7909                         timeIncrement, appData.searchDepth,
7910                         searchTime);
7911     }
7912     if (appData.showThinking 
7913         // [HGM] thinking: four options require thinking output to be sent
7914         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7915                                 ) {
7916         SendToProgram("post\n", cps);
7917     }
7918     SendToProgram("hard\n", cps);
7919     if (!appData.ponderNextMove) {
7920         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7921            it without being sure what state we are in first.  "hard"
7922            is not a toggle, so that one is OK.
7923          */
7924         SendToProgram("easy\n", cps);
7925     }
7926     if (cps->usePing) {
7927       sprintf(buf, "ping %d\n", ++cps->lastPing);
7928       SendToProgram(buf, cps);
7929     }
7930     cps->initDone = TRUE;
7931 }   
7932
7933
7934 void
7935 StartChessProgram(cps)
7936      ChessProgramState *cps;
7937 {
7938     char buf[MSG_SIZ];
7939     int err;
7940
7941     if (appData.noChessProgram) return;
7942     cps->initDone = FALSE;
7943
7944     if (strcmp(cps->host, "localhost") == 0) {
7945         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7946     } else if (*appData.remoteShell == NULLCHAR) {
7947         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7948     } else {
7949         if (*appData.remoteUser == NULLCHAR) {
7950           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7951                     cps->program);
7952         } else {
7953           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7954                     cps->host, appData.remoteUser, cps->program);
7955         }
7956         err = StartChildProcess(buf, "", &cps->pr);
7957     }
7958     
7959     if (err != 0) {
7960         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7961         DisplayFatalError(buf, err, 1);
7962         cps->pr = NoProc;
7963         cps->isr = NULL;
7964         return;
7965     }
7966     
7967     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7968     if (cps->protocolVersion > 1) {
7969       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7970       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7971       cps->comboCnt = 0;  //                and values of combo boxes
7972       SendToProgram(buf, cps);
7973     } else {
7974       SendToProgram("xboard\n", cps);
7975     }
7976 }
7977
7978
7979 void
7980 TwoMachinesEventIfReady P((void))
7981 {
7982   if (first.lastPing != first.lastPong) {
7983     DisplayMessage("", _("Waiting for first chess program"));
7984     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7985     return;
7986   }
7987   if (second.lastPing != second.lastPong) {
7988     DisplayMessage("", _("Waiting for second chess program"));
7989     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7990     return;
7991   }
7992   ThawUI();
7993   TwoMachinesEvent();
7994 }
7995
7996 void
7997 NextMatchGame P((void))
7998 {
7999     int index; /* [HGM] autoinc: step load index during match */
8000     Reset(FALSE, TRUE);
8001     if (*appData.loadGameFile != NULLCHAR) {
8002         index = appData.loadGameIndex;
8003         if(index < 0) { // [HGM] autoinc
8004             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8005             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8006         } 
8007         LoadGameFromFile(appData.loadGameFile,
8008                          index,
8009                          appData.loadGameFile, FALSE);
8010     } else if (*appData.loadPositionFile != NULLCHAR) {
8011         index = appData.loadPositionIndex;
8012         if(index < 0) { // [HGM] autoinc
8013             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8014             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8015         } 
8016         LoadPositionFromFile(appData.loadPositionFile,
8017                              index,
8018                              appData.loadPositionFile);
8019     }
8020     TwoMachinesEventIfReady();
8021 }
8022
8023 void UserAdjudicationEvent( int result )
8024 {
8025     ChessMove gameResult = GameIsDrawn;
8026
8027     if( result > 0 ) {
8028         gameResult = WhiteWins;
8029     }
8030     else if( result < 0 ) {
8031         gameResult = BlackWins;
8032     }
8033
8034     if( gameMode == TwoMachinesPlay ) {
8035         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8036     }
8037 }
8038
8039
8040 // [HGM] save: calculate checksum of game to make games easily identifiable
8041 int StringCheckSum(char *s)
8042 {
8043         int i = 0;
8044         if(s==NULL) return 0;
8045         while(*s) i = i*259 + *s++;
8046         return i;
8047 }
8048
8049 int GameCheckSum()
8050 {
8051         int i, sum=0;
8052         for(i=backwardMostMove; i<forwardMostMove; i++) {
8053                 sum += pvInfoList[i].depth;
8054                 sum += StringCheckSum(parseList[i]);
8055                 sum += StringCheckSum(commentList[i]);
8056                 sum *= 261;
8057         }
8058         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8059         return sum + StringCheckSum(commentList[i]);
8060 } // end of save patch
8061
8062 void
8063 GameEnds(result, resultDetails, whosays)
8064      ChessMove result;
8065      char *resultDetails;
8066      int whosays;
8067 {
8068     GameMode nextGameMode;
8069     int isIcsGame;
8070     char buf[MSG_SIZ];
8071
8072     if(endingGame) return; /* [HGM] crash: forbid recursion */
8073     endingGame = 1;
8074
8075     if (appData.debugMode) {
8076       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8077               result, resultDetails ? resultDetails : "(null)", whosays);
8078     }
8079
8080     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8081         /* If we are playing on ICS, the server decides when the
8082            game is over, but the engine can offer to draw, claim 
8083            a draw, or resign. 
8084          */
8085 #if ZIPPY
8086         if (appData.zippyPlay && first.initDone) {
8087             if (result == GameIsDrawn) {
8088                 /* In case draw still needs to be claimed */
8089                 SendToICS(ics_prefix);
8090                 SendToICS("draw\n");
8091             } else if (StrCaseStr(resultDetails, "resign")) {
8092                 SendToICS(ics_prefix);
8093                 SendToICS("resign\n");
8094             }
8095         }
8096 #endif
8097         endingGame = 0; /* [HGM] crash */
8098         return;
8099     }
8100
8101     /* If we're loading the game from a file, stop */
8102     if (whosays == GE_FILE) {
8103       (void) StopLoadGameTimer();
8104       gameFileFP = NULL;
8105     }
8106
8107     /* Cancel draw offers */
8108     first.offeredDraw = second.offeredDraw = 0;
8109
8110     /* If this is an ICS game, only ICS can really say it's done;
8111        if not, anyone can. */
8112     isIcsGame = (gameMode == IcsPlayingWhite || 
8113                  gameMode == IcsPlayingBlack || 
8114                  gameMode == IcsObserving    || 
8115                  gameMode == IcsExamining);
8116
8117     if (!isIcsGame || whosays == GE_ICS) {
8118         /* OK -- not an ICS game, or ICS said it was done */
8119         StopClocks();
8120         if (!isIcsGame && !appData.noChessProgram) 
8121           SetUserThinkingEnables();
8122     
8123         /* [HGM] if a machine claims the game end we verify this claim */
8124         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8125             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8126                 char claimer;
8127                 ChessMove trueResult = (ChessMove) -1;
8128
8129                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8130                                             first.twoMachinesColor[0] :
8131                                             second.twoMachinesColor[0] ;
8132
8133                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8134                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8135                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8136                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8137                 } else
8138                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8139                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8140                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8141                 } else
8142                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8143                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8144                 }
8145
8146                 // now verify win claims, but not in drop games, as we don't understand those yet
8147                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8148                                                  || gameInfo.variant == VariantGreat) &&
8149                     (result == WhiteWins && claimer == 'w' ||
8150                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8151                       if (appData.debugMode) {
8152                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8153                                 result, epStatus[forwardMostMove], forwardMostMove);
8154                       }
8155                       if(result != trueResult) {
8156                               sprintf(buf, "False win claim: '%s'", resultDetails);
8157                               result = claimer == 'w' ? BlackWins : WhiteWins;
8158                               resultDetails = buf;
8159                       }
8160                 } else
8161                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8162                     && (forwardMostMove <= backwardMostMove ||
8163                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8164                         (claimer=='b')==(forwardMostMove&1))
8165                                                                                   ) {
8166                       /* [HGM] verify: draws that were not flagged are false claims */
8167                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8168                       result = claimer == 'w' ? BlackWins : WhiteWins;
8169                       resultDetails = buf;
8170                 }
8171                 /* (Claiming a loss is accepted no questions asked!) */
8172             }
8173             /* [HGM] bare: don't allow bare King to win */
8174             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8175                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8176                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8177                && result != GameIsDrawn)
8178             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8179                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8180                         int p = (int)boards[forwardMostMove][i][j] - color;
8181                         if(p >= 0 && p <= (int)WhiteKing) k++;
8182                 }
8183                 if (appData.debugMode) {
8184                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8185                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8186                 }
8187                 if(k <= 1) {
8188                         result = GameIsDrawn;
8189                         sprintf(buf, "%s but bare king", resultDetails);
8190                         resultDetails = buf;
8191                 }
8192             }
8193         }
8194
8195
8196         if(serverMoves != NULL && !loadFlag) { char c = '=';
8197             if(result==WhiteWins) c = '+';
8198             if(result==BlackWins) c = '-';
8199             if(resultDetails != NULL)
8200                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8201         }
8202         if (resultDetails != NULL) {
8203             gameInfo.result = result;
8204             gameInfo.resultDetails = StrSave(resultDetails);
8205
8206             /* display last move only if game was not loaded from file */
8207             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8208                 DisplayMove(currentMove - 1);
8209     
8210             if (forwardMostMove != 0) {
8211                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8212                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8213                                                                 ) {
8214                     if (*appData.saveGameFile != NULLCHAR) {
8215                         SaveGameToFile(appData.saveGameFile, TRUE);
8216                     } else if (appData.autoSaveGames) {
8217                         AutoSaveGame();
8218                     }
8219                     if (*appData.savePositionFile != NULLCHAR) {
8220                         SavePositionToFile(appData.savePositionFile);
8221                     }
8222                 }
8223             }
8224
8225             /* Tell program how game ended in case it is learning */
8226             /* [HGM] Moved this to after saving the PGN, just in case */
8227             /* engine died and we got here through time loss. In that */
8228             /* case we will get a fatal error writing the pipe, which */
8229             /* would otherwise lose us the PGN.                       */
8230             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8231             /* output during GameEnds should never be fatal anymore   */
8232             if (gameMode == MachinePlaysWhite ||
8233                 gameMode == MachinePlaysBlack ||
8234                 gameMode == TwoMachinesPlay ||
8235                 gameMode == IcsPlayingWhite ||
8236                 gameMode == IcsPlayingBlack ||
8237                 gameMode == BeginningOfGame) {
8238                 char buf[MSG_SIZ];
8239                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8240                         resultDetails);
8241                 if (first.pr != NoProc) {
8242                     SendToProgram(buf, &first);
8243                 }
8244                 if (second.pr != NoProc &&
8245                     gameMode == TwoMachinesPlay) {
8246                     SendToProgram(buf, &second);
8247                 }
8248             }
8249         }
8250
8251         if (appData.icsActive) {
8252             if (appData.quietPlay &&
8253                 (gameMode == IcsPlayingWhite ||
8254                  gameMode == IcsPlayingBlack)) {
8255                 SendToICS(ics_prefix);
8256                 SendToICS("set shout 1\n");
8257             }
8258             nextGameMode = IcsIdle;
8259             ics_user_moved = FALSE;
8260             /* clean up premove.  It's ugly when the game has ended and the
8261              * premove highlights are still on the board.
8262              */
8263             if (gotPremove) {
8264               gotPremove = FALSE;
8265               ClearPremoveHighlights();
8266               DrawPosition(FALSE, boards[currentMove]);
8267             }
8268             if (whosays == GE_ICS) {
8269                 switch (result) {
8270                 case WhiteWins:
8271                     if (gameMode == IcsPlayingWhite)
8272                         PlayIcsWinSound();
8273                     else if(gameMode == IcsPlayingBlack)
8274                         PlayIcsLossSound();
8275                     break;
8276                 case BlackWins:
8277                     if (gameMode == IcsPlayingBlack)
8278                         PlayIcsWinSound();
8279                     else if(gameMode == IcsPlayingWhite)
8280                         PlayIcsLossSound();
8281                     break;
8282                 case GameIsDrawn:
8283                     PlayIcsDrawSound();
8284                     break;
8285                 default:
8286                     PlayIcsUnfinishedSound();
8287                 }
8288             }
8289         } else if (gameMode == EditGame ||
8290                    gameMode == PlayFromGameFile || 
8291                    gameMode == AnalyzeMode || 
8292                    gameMode == AnalyzeFile) {
8293             nextGameMode = gameMode;
8294         } else {
8295             nextGameMode = EndOfGame;
8296         }
8297         pausing = FALSE;
8298         ModeHighlight();
8299     } else {
8300         nextGameMode = gameMode;
8301     }
8302
8303     if (appData.noChessProgram) {
8304         gameMode = nextGameMode;
8305         ModeHighlight();
8306         endingGame = 0; /* [HGM] crash */
8307         return;
8308     }
8309
8310     if (first.reuse) {
8311         /* Put first chess program into idle state */
8312         if (first.pr != NoProc &&
8313             (gameMode == MachinePlaysWhite ||
8314              gameMode == MachinePlaysBlack ||
8315              gameMode == TwoMachinesPlay ||
8316              gameMode == IcsPlayingWhite ||
8317              gameMode == IcsPlayingBlack ||
8318              gameMode == BeginningOfGame)) {
8319             SendToProgram("force\n", &first);
8320             if (first.usePing) {
8321               char buf[MSG_SIZ];
8322               sprintf(buf, "ping %d\n", ++first.lastPing);
8323               SendToProgram(buf, &first);
8324             }
8325         }
8326     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8327         /* Kill off first chess program */
8328         if (first.isr != NULL)
8329           RemoveInputSource(first.isr);
8330         first.isr = NULL;
8331     
8332         if (first.pr != NoProc) {
8333             ExitAnalyzeMode();
8334             DoSleep( appData.delayBeforeQuit );
8335             SendToProgram("quit\n", &first);
8336             DoSleep( appData.delayAfterQuit );
8337             DestroyChildProcess(first.pr, first.useSigterm);
8338         }
8339         first.pr = NoProc;
8340     }
8341     if (second.reuse) {
8342         /* Put second chess program into idle state */
8343         if (second.pr != NoProc &&
8344             gameMode == TwoMachinesPlay) {
8345             SendToProgram("force\n", &second);
8346             if (second.usePing) {
8347               char buf[MSG_SIZ];
8348               sprintf(buf, "ping %d\n", ++second.lastPing);
8349               SendToProgram(buf, &second);
8350             }
8351         }
8352     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8353         /* Kill off second chess program */
8354         if (second.isr != NULL)
8355           RemoveInputSource(second.isr);
8356         second.isr = NULL;
8357     
8358         if (second.pr != NoProc) {
8359             DoSleep( appData.delayBeforeQuit );
8360             SendToProgram("quit\n", &second);
8361             DoSleep( appData.delayAfterQuit );
8362             DestroyChildProcess(second.pr, second.useSigterm);
8363         }
8364         second.pr = NoProc;
8365     }
8366
8367     if (matchMode && gameMode == TwoMachinesPlay) {
8368         switch (result) {
8369         case WhiteWins:
8370           if (first.twoMachinesColor[0] == 'w') {
8371             first.matchWins++;
8372           } else {
8373             second.matchWins++;
8374           }
8375           break;
8376         case BlackWins:
8377           if (first.twoMachinesColor[0] == 'b') {
8378             first.matchWins++;
8379           } else {
8380             second.matchWins++;
8381           }
8382           break;
8383         default:
8384           break;
8385         }
8386         if (matchGame < appData.matchGames) {
8387             char *tmp;
8388             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8389                 tmp = first.twoMachinesColor;
8390                 first.twoMachinesColor = second.twoMachinesColor;
8391                 second.twoMachinesColor = tmp;
8392             }
8393             gameMode = nextGameMode;
8394             matchGame++;
8395             if(appData.matchPause>10000 || appData.matchPause<10)
8396                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8397             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8398             endingGame = 0; /* [HGM] crash */
8399             return;
8400         } else {
8401             char buf[MSG_SIZ];
8402             gameMode = nextGameMode;
8403             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8404                     first.tidy, second.tidy,
8405                     first.matchWins, second.matchWins,
8406                     appData.matchGames - (first.matchWins + second.matchWins));
8407             DisplayFatalError(buf, 0, 0);
8408         }
8409     }
8410     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8411         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8412       ExitAnalyzeMode();
8413     gameMode = nextGameMode;
8414     ModeHighlight();
8415     endingGame = 0;  /* [HGM] crash */
8416 }
8417
8418 /* Assumes program was just initialized (initString sent).
8419    Leaves program in force mode. */
8420 void
8421 FeedMovesToProgram(cps, upto) 
8422      ChessProgramState *cps;
8423      int upto;
8424 {
8425     int i;
8426     
8427     if (appData.debugMode)
8428       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8429               startedFromSetupPosition ? "position and " : "",
8430               backwardMostMove, upto, cps->which);
8431     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8432         // [HGM] variantswitch: make engine aware of new variant
8433         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8434                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8435         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8436         SendToProgram(buf, cps);
8437         currentlyInitializedVariant = gameInfo.variant;
8438     }
8439     SendToProgram("force\n", cps);
8440     if (startedFromSetupPosition) {
8441         SendBoard(cps, backwardMostMove);
8442     if (appData.debugMode) {
8443         fprintf(debugFP, "feedMoves\n");
8444     }
8445     }
8446     for (i = backwardMostMove; i < upto; i++) {
8447         SendMoveToProgram(i, cps);
8448     }
8449 }
8450
8451
8452 void
8453 ResurrectChessProgram()
8454 {
8455      /* The chess program may have exited.
8456         If so, restart it and feed it all the moves made so far. */
8457
8458     if (appData.noChessProgram || first.pr != NoProc) return;
8459     
8460     StartChessProgram(&first);
8461     InitChessProgram(&first, FALSE);
8462     FeedMovesToProgram(&first, currentMove);
8463
8464     if (!first.sendTime) {
8465         /* can't tell gnuchess what its clock should read,
8466            so we bow to its notion. */
8467         ResetClocks();
8468         timeRemaining[0][currentMove] = whiteTimeRemaining;
8469         timeRemaining[1][currentMove] = blackTimeRemaining;
8470     }
8471
8472     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8473                 appData.icsEngineAnalyze) && first.analysisSupport) {
8474       SendToProgram("analyze\n", &first);
8475       first.analyzing = TRUE;
8476     }
8477 }
8478
8479 /*
8480  * Button procedures
8481  */
8482 void
8483 Reset(redraw, init)
8484      int redraw, init;
8485 {
8486     int i;
8487
8488     if (appData.debugMode) {
8489         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8490                 redraw, init, gameMode);
8491     }
8492     pausing = pauseExamInvalid = FALSE;
8493     startedFromSetupPosition = blackPlaysFirst = FALSE;
8494     firstMove = TRUE;
8495     whiteFlag = blackFlag = FALSE;
8496     userOfferedDraw = FALSE;
8497     hintRequested = bookRequested = FALSE;
8498     first.maybeThinking = FALSE;
8499     second.maybeThinking = FALSE;
8500     first.bookSuspend = FALSE; // [HGM] book
8501     second.bookSuspend = FALSE;
8502     thinkOutput[0] = NULLCHAR;
8503     lastHint[0] = NULLCHAR;
8504     ClearGameInfo(&gameInfo);
8505     gameInfo.variant = StringToVariant(appData.variant);
8506     ics_user_moved = ics_clock_paused = FALSE;
8507     ics_getting_history = H_FALSE;
8508     ics_gamenum = -1;
8509     white_holding[0] = black_holding[0] = NULLCHAR;
8510     ClearProgramStats();
8511     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8512     
8513     ResetFrontEnd();
8514     ClearHighlights();
8515     flipView = appData.flipView;
8516     ClearPremoveHighlights();
8517     gotPremove = FALSE;
8518     alarmSounded = FALSE;
8519
8520     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8521     if(appData.serverMovesName != NULL) {
8522         /* [HGM] prepare to make moves file for broadcasting */
8523         clock_t t = clock();
8524         if(serverMoves != NULL) fclose(serverMoves);
8525         serverMoves = fopen(appData.serverMovesName, "r");
8526         if(serverMoves != NULL) {
8527             fclose(serverMoves);
8528             /* delay 15 sec before overwriting, so all clients can see end */
8529             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8530         }
8531         serverMoves = fopen(appData.serverMovesName, "w");
8532     }
8533
8534     ExitAnalyzeMode();
8535     gameMode = BeginningOfGame;
8536     ModeHighlight();
8537     if(appData.icsActive) gameInfo.variant = VariantNormal;
8538     currentMove = forwardMostMove = backwardMostMove = 0;
8539     InitPosition(redraw);
8540     for (i = 0; i < MAX_MOVES; i++) {
8541         if (commentList[i] != NULL) {
8542             free(commentList[i]);
8543             commentList[i] = NULL;
8544         }
8545     }
8546     ResetClocks();
8547     timeRemaining[0][0] = whiteTimeRemaining;
8548     timeRemaining[1][0] = blackTimeRemaining;
8549     if (first.pr == NULL) {
8550         StartChessProgram(&first);
8551     }
8552     if (init) {
8553             InitChessProgram(&first, startedFromSetupPosition);
8554     }
8555     DisplayTitle("");
8556     DisplayMessage("", "");
8557     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8558     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8559 }
8560
8561 void
8562 AutoPlayGameLoop()
8563 {
8564     for (;;) {
8565         if (!AutoPlayOneMove())
8566           return;
8567         if (matchMode || appData.timeDelay == 0)
8568           continue;
8569         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8570           return;
8571         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8572         break;
8573     }
8574 }
8575
8576
8577 int
8578 AutoPlayOneMove()
8579 {
8580     int fromX, fromY, toX, toY;
8581
8582     if (appData.debugMode) {
8583       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8584     }
8585
8586     if (gameMode != PlayFromGameFile)
8587       return FALSE;
8588
8589     if (currentMove >= forwardMostMove) {
8590       gameMode = EditGame;
8591       ModeHighlight();
8592
8593       /* [AS] Clear current move marker at the end of a game */
8594       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8595
8596       return FALSE;
8597     }
8598     
8599     toX = moveList[currentMove][2] - AAA;
8600     toY = moveList[currentMove][3] - ONE;
8601
8602     if (moveList[currentMove][1] == '@') {
8603         if (appData.highlightLastMove) {
8604             SetHighlights(-1, -1, toX, toY);
8605         }
8606     } else {
8607         fromX = moveList[currentMove][0] - AAA;
8608         fromY = moveList[currentMove][1] - ONE;
8609
8610         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8611
8612         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8613
8614         if (appData.highlightLastMove) {
8615             SetHighlights(fromX, fromY, toX, toY);
8616         }
8617     }
8618     DisplayMove(currentMove);
8619     SendMoveToProgram(currentMove++, &first);
8620     DisplayBothClocks();
8621     DrawPosition(FALSE, boards[currentMove]);
8622     // [HGM] PV info: always display, routine tests if empty
8623     DisplayComment(currentMove - 1, commentList[currentMove]);
8624     return TRUE;
8625 }
8626
8627
8628 int
8629 LoadGameOneMove(readAhead)
8630      ChessMove readAhead;
8631 {
8632     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8633     char promoChar = NULLCHAR;
8634     ChessMove moveType;
8635     char move[MSG_SIZ];
8636     char *p, *q;
8637     
8638     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8639         gameMode != AnalyzeMode && gameMode != Training) {
8640         gameFileFP = NULL;
8641         return FALSE;
8642     }
8643     
8644     yyboardindex = forwardMostMove;
8645     if (readAhead != (ChessMove)0) {
8646       moveType = readAhead;
8647     } else {
8648       if (gameFileFP == NULL)
8649           return FALSE;
8650       moveType = (ChessMove) yylex();
8651     }
8652     
8653     done = FALSE;
8654     switch (moveType) {
8655       case Comment:
8656         if (appData.debugMode) 
8657           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8658         p = yy_text;
8659         if (*p == '{' || *p == '[' || *p == '(') {
8660             p[strlen(p) - 1] = NULLCHAR;
8661             p++;
8662         }
8663
8664         /* append the comment but don't display it */
8665         while (*p == '\n') p++;
8666         AppendComment(currentMove, p);
8667         return TRUE;
8668
8669       case WhiteCapturesEnPassant:
8670       case BlackCapturesEnPassant:
8671       case WhitePromotionChancellor:
8672       case BlackPromotionChancellor:
8673       case WhitePromotionArchbishop:
8674       case BlackPromotionArchbishop:
8675       case WhitePromotionCentaur:
8676       case BlackPromotionCentaur:
8677       case WhitePromotionQueen:
8678       case BlackPromotionQueen:
8679       case WhitePromotionRook:
8680       case BlackPromotionRook:
8681       case WhitePromotionBishop:
8682       case BlackPromotionBishop:
8683       case WhitePromotionKnight:
8684       case BlackPromotionKnight:
8685       case WhitePromotionKing:
8686       case BlackPromotionKing:
8687       case NormalMove:
8688       case WhiteKingSideCastle:
8689       case WhiteQueenSideCastle:
8690       case BlackKingSideCastle:
8691       case BlackQueenSideCastle:
8692       case WhiteKingSideCastleWild:
8693       case WhiteQueenSideCastleWild:
8694       case BlackKingSideCastleWild:
8695       case BlackQueenSideCastleWild:
8696       /* PUSH Fabien */
8697       case WhiteHSideCastleFR:
8698       case WhiteASideCastleFR:
8699       case BlackHSideCastleFR:
8700       case BlackASideCastleFR:
8701       /* POP Fabien */
8702         if (appData.debugMode)
8703           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8704         fromX = currentMoveString[0] - AAA;
8705         fromY = currentMoveString[1] - ONE;
8706         toX = currentMoveString[2] - AAA;
8707         toY = currentMoveString[3] - ONE;
8708         promoChar = currentMoveString[4];
8709         break;
8710
8711       case WhiteDrop:
8712       case BlackDrop:
8713         if (appData.debugMode)
8714           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8715         fromX = moveType == WhiteDrop ?
8716           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8717         (int) CharToPiece(ToLower(currentMoveString[0]));
8718         fromY = DROP_RANK;
8719         toX = currentMoveString[2] - AAA;
8720         toY = currentMoveString[3] - ONE;
8721         break;
8722
8723       case WhiteWins:
8724       case BlackWins:
8725       case GameIsDrawn:
8726       case GameUnfinished:
8727         if (appData.debugMode)
8728           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8729         p = strchr(yy_text, '{');
8730         if (p == NULL) p = strchr(yy_text, '(');
8731         if (p == NULL) {
8732             p = yy_text;
8733             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8734         } else {
8735             q = strchr(p, *p == '{' ? '}' : ')');
8736             if (q != NULL) *q = NULLCHAR;
8737             p++;
8738         }
8739         GameEnds(moveType, p, GE_FILE);
8740         done = TRUE;
8741         if (cmailMsgLoaded) {
8742             ClearHighlights();
8743             flipView = WhiteOnMove(currentMove);
8744             if (moveType == GameUnfinished) flipView = !flipView;
8745             if (appData.debugMode)
8746               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8747         }
8748         break;
8749
8750       case (ChessMove) 0:       /* end of file */
8751         if (appData.debugMode)
8752           fprintf(debugFP, "Parser hit end of file\n");
8753         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8754                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8755           case MT_NONE:
8756           case MT_CHECK:
8757             break;
8758           case MT_CHECKMATE:
8759           case MT_STAINMATE:
8760             if (WhiteOnMove(currentMove)) {
8761                 GameEnds(BlackWins, "Black mates", GE_FILE);
8762             } else {
8763                 GameEnds(WhiteWins, "White mates", GE_FILE);
8764             }
8765             break;
8766           case MT_STALEMATE:
8767             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8768             break;
8769         }
8770         done = TRUE;
8771         break;
8772
8773       case MoveNumberOne:
8774         if (lastLoadGameStart == GNUChessGame) {
8775             /* GNUChessGames have numbers, but they aren't move numbers */
8776             if (appData.debugMode)
8777               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8778                       yy_text, (int) moveType);
8779             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8780         }
8781         /* else fall thru */
8782
8783       case XBoardGame:
8784       case GNUChessGame:
8785       case PGNTag:
8786         /* Reached start of next game in file */
8787         if (appData.debugMode)
8788           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8789         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8790                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8791           case MT_NONE:
8792           case MT_CHECK:
8793             break;
8794           case MT_CHECKMATE:
8795           case MT_STAINMATE:
8796             if (WhiteOnMove(currentMove)) {
8797                 GameEnds(BlackWins, "Black mates", GE_FILE);
8798             } else {
8799                 GameEnds(WhiteWins, "White mates", GE_FILE);
8800             }
8801             break;
8802           case MT_STALEMATE:
8803             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8804             break;
8805         }
8806         done = TRUE;
8807         break;
8808
8809       case PositionDiagram:     /* should not happen; ignore */
8810       case ElapsedTime:         /* ignore */
8811       case NAG:                 /* ignore */
8812         if (appData.debugMode)
8813           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8814                   yy_text, (int) moveType);
8815         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8816
8817       case IllegalMove:
8818         if (appData.testLegality) {
8819             if (appData.debugMode)
8820               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8821             sprintf(move, _("Illegal move: %d.%s%s"),
8822                     (forwardMostMove / 2) + 1,
8823                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8824             DisplayError(move, 0);
8825             done = TRUE;
8826         } else {
8827             if (appData.debugMode)
8828               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8829                       yy_text, currentMoveString);
8830             fromX = currentMoveString[0] - AAA;
8831             fromY = currentMoveString[1] - ONE;
8832             toX = currentMoveString[2] - AAA;
8833             toY = currentMoveString[3] - ONE;
8834             promoChar = currentMoveString[4];
8835         }
8836         break;
8837
8838       case AmbiguousMove:
8839         if (appData.debugMode)
8840           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8841         sprintf(move, _("Ambiguous move: %d.%s%s"),
8842                 (forwardMostMove / 2) + 1,
8843                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8844         DisplayError(move, 0);
8845         done = TRUE;
8846         break;
8847
8848       default:
8849       case ImpossibleMove:
8850         if (appData.debugMode)
8851           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8852         sprintf(move, _("Illegal move: %d.%s%s"),
8853                 (forwardMostMove / 2) + 1,
8854                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8855         DisplayError(move, 0);
8856         done = TRUE;
8857         break;
8858     }
8859
8860     if (done) {
8861         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8862             DrawPosition(FALSE, boards[currentMove]);
8863             DisplayBothClocks();
8864             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8865               DisplayComment(currentMove - 1, commentList[currentMove]);
8866         }
8867         (void) StopLoadGameTimer();
8868         gameFileFP = NULL;
8869         cmailOldMove = forwardMostMove;
8870         return FALSE;
8871     } else {
8872         /* currentMoveString is set as a side-effect of yylex */
8873         strcat(currentMoveString, "\n");
8874         strcpy(moveList[forwardMostMove], currentMoveString);
8875         
8876         thinkOutput[0] = NULLCHAR;
8877         MakeMove(fromX, fromY, toX, toY, promoChar);
8878         currentMove = forwardMostMove;
8879         return TRUE;
8880     }
8881 }
8882
8883 /* Load the nth game from the given file */
8884 int
8885 LoadGameFromFile(filename, n, title, useList)
8886      char *filename;
8887      int n;
8888      char *title;
8889      /*Boolean*/ int useList;
8890 {
8891     FILE *f;
8892     char buf[MSG_SIZ];
8893
8894     if (strcmp(filename, "-") == 0) {
8895         f = stdin;
8896         title = "stdin";
8897     } else {
8898         f = fopen(filename, "rb");
8899         if (f == NULL) {
8900           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8901             DisplayError(buf, errno);
8902             return FALSE;
8903         }
8904     }
8905     if (fseek(f, 0, 0) == -1) {
8906         /* f is not seekable; probably a pipe */
8907         useList = FALSE;
8908     }
8909     if (useList && n == 0) {
8910         int error = GameListBuild(f);
8911         if (error) {
8912             DisplayError(_("Cannot build game list"), error);
8913         } else if (!ListEmpty(&gameList) &&
8914                    ((ListGame *) gameList.tailPred)->number > 1) {
8915             GameListPopUp(f, title);
8916             return TRUE;
8917         }
8918         GameListDestroy();
8919         n = 1;
8920     }
8921     if (n == 0) n = 1;
8922     return LoadGame(f, n, title, FALSE);
8923 }
8924
8925
8926 void
8927 MakeRegisteredMove()
8928 {
8929     int fromX, fromY, toX, toY;
8930     char promoChar;
8931     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8932         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8933           case CMAIL_MOVE:
8934           case CMAIL_DRAW:
8935             if (appData.debugMode)
8936               fprintf(debugFP, "Restoring %s for game %d\n",
8937                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8938     
8939             thinkOutput[0] = NULLCHAR;
8940             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8941             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8942             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8943             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8944             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8945             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8946             MakeMove(fromX, fromY, toX, toY, promoChar);
8947             ShowMove(fromX, fromY, toX, toY);
8948               
8949             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8950                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8951               case MT_NONE:
8952               case MT_CHECK:
8953                 break;
8954                 
8955               case MT_CHECKMATE:
8956               case MT_STAINMATE:
8957                 if (WhiteOnMove(currentMove)) {
8958                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8959                 } else {
8960                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8961                 }
8962                 break;
8963                 
8964               case MT_STALEMATE:
8965                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8966                 break;
8967             }
8968
8969             break;
8970             
8971           case CMAIL_RESIGN:
8972             if (WhiteOnMove(currentMove)) {
8973                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8974             } else {
8975                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8976             }
8977             break;
8978             
8979           case CMAIL_ACCEPT:
8980             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8981             break;
8982               
8983           default:
8984             break;
8985         }
8986     }
8987
8988     return;
8989 }
8990
8991 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8992 int
8993 CmailLoadGame(f, gameNumber, title, useList)
8994      FILE *f;
8995      int gameNumber;
8996      char *title;
8997      int useList;
8998 {
8999     int retVal;
9000
9001     if (gameNumber > nCmailGames) {
9002         DisplayError(_("No more games in this message"), 0);
9003         return FALSE;
9004     }
9005     if (f == lastLoadGameFP) {
9006         int offset = gameNumber - lastLoadGameNumber;
9007         if (offset == 0) {
9008             cmailMsg[0] = NULLCHAR;
9009             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9010                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9011                 nCmailMovesRegistered--;
9012             }
9013             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9014             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9015                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9016             }
9017         } else {
9018             if (! RegisterMove()) return FALSE;
9019         }
9020     }
9021
9022     retVal = LoadGame(f, gameNumber, title, useList);
9023
9024     /* Make move registered during previous look at this game, if any */
9025     MakeRegisteredMove();
9026
9027     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9028         commentList[currentMove]
9029           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9030         DisplayComment(currentMove - 1, commentList[currentMove]);
9031     }
9032
9033     return retVal;
9034 }
9035
9036 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9037 int
9038 ReloadGame(offset)
9039      int offset;
9040 {
9041     int gameNumber = lastLoadGameNumber + offset;
9042     if (lastLoadGameFP == NULL) {
9043         DisplayError(_("No game has been loaded yet"), 0);
9044         return FALSE;
9045     }
9046     if (gameNumber <= 0) {
9047         DisplayError(_("Can't back up any further"), 0);
9048         return FALSE;
9049     }
9050     if (cmailMsgLoaded) {
9051         return CmailLoadGame(lastLoadGameFP, gameNumber,
9052                              lastLoadGameTitle, lastLoadGameUseList);
9053     } else {
9054         return LoadGame(lastLoadGameFP, gameNumber,
9055                         lastLoadGameTitle, lastLoadGameUseList);
9056     }
9057 }
9058
9059
9060
9061 /* Load the nth game from open file f */
9062 int
9063 LoadGame(f, gameNumber, title, useList)
9064      FILE *f;
9065      int gameNumber;
9066      char *title;
9067      int useList;
9068 {
9069     ChessMove cm;
9070     char buf[MSG_SIZ];
9071     int gn = gameNumber;
9072     ListGame *lg = NULL;
9073     int numPGNTags = 0;
9074     int err;
9075     GameMode oldGameMode;
9076     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9077
9078     if (appData.debugMode) 
9079         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9080
9081     if (gameMode == Training )
9082         SetTrainingModeOff();
9083
9084     oldGameMode = gameMode;
9085     if (gameMode != BeginningOfGame) {
9086       Reset(FALSE, TRUE);
9087     }
9088
9089     gameFileFP = f;
9090     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9091         fclose(lastLoadGameFP);
9092     }
9093
9094     if (useList) {
9095         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9096         
9097         if (lg) {
9098             fseek(f, lg->offset, 0);
9099             GameListHighlight(gameNumber);
9100             gn = 1;
9101         }
9102         else {
9103             DisplayError(_("Game number out of range"), 0);
9104             return FALSE;
9105         }
9106     } else {
9107         GameListDestroy();
9108         if (fseek(f, 0, 0) == -1) {
9109             if (f == lastLoadGameFP ?
9110                 gameNumber == lastLoadGameNumber + 1 :
9111                 gameNumber == 1) {
9112                 gn = 1;
9113             } else {
9114                 DisplayError(_("Can't seek on game file"), 0);
9115                 return FALSE;
9116             }
9117         }
9118     }
9119     lastLoadGameFP = f;
9120     lastLoadGameNumber = gameNumber;
9121     strcpy(lastLoadGameTitle, title);
9122     lastLoadGameUseList = useList;
9123
9124     yynewfile(f);
9125
9126     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9127       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9128                 lg->gameInfo.black);
9129             DisplayTitle(buf);
9130     } else if (*title != NULLCHAR) {
9131         if (gameNumber > 1) {
9132             sprintf(buf, "%s %d", title, gameNumber);
9133             DisplayTitle(buf);
9134         } else {
9135             DisplayTitle(title);
9136         }
9137     }
9138
9139     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9140         gameMode = PlayFromGameFile;
9141         ModeHighlight();
9142     }
9143
9144     currentMove = forwardMostMove = backwardMostMove = 0;
9145     CopyBoard(boards[0], initialPosition);
9146     StopClocks();
9147
9148     /*
9149      * Skip the first gn-1 games in the file.
9150      * Also skip over anything that precedes an identifiable 
9151      * start of game marker, to avoid being confused by 
9152      * garbage at the start of the file.  Currently 
9153      * recognized start of game markers are the move number "1",
9154      * the pattern "gnuchess .* game", the pattern
9155      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9156      * A game that starts with one of the latter two patterns
9157      * will also have a move number 1, possibly
9158      * following a position diagram.
9159      * 5-4-02: Let's try being more lenient and allowing a game to
9160      * start with an unnumbered move.  Does that break anything?
9161      */
9162     cm = lastLoadGameStart = (ChessMove) 0;
9163     while (gn > 0) {
9164         yyboardindex = forwardMostMove;
9165         cm = (ChessMove) yylex();
9166         switch (cm) {
9167           case (ChessMove) 0:
9168             if (cmailMsgLoaded) {
9169                 nCmailGames = CMAIL_MAX_GAMES - gn;
9170             } else {
9171                 Reset(TRUE, TRUE);
9172                 DisplayError(_("Game not found in file"), 0);
9173             }
9174             return FALSE;
9175
9176           case GNUChessGame:
9177           case XBoardGame:
9178             gn--;
9179             lastLoadGameStart = cm;
9180             break;
9181             
9182           case MoveNumberOne:
9183             switch (lastLoadGameStart) {
9184               case GNUChessGame:
9185               case XBoardGame:
9186               case PGNTag:
9187                 break;
9188               case MoveNumberOne:
9189               case (ChessMove) 0:
9190                 gn--;           /* count this game */
9191                 lastLoadGameStart = cm;
9192                 break;
9193               default:
9194                 /* impossible */
9195                 break;
9196             }
9197             break;
9198
9199           case PGNTag:
9200             switch (lastLoadGameStart) {
9201               case GNUChessGame:
9202               case PGNTag:
9203               case MoveNumberOne:
9204               case (ChessMove) 0:
9205                 gn--;           /* count this game */
9206                 lastLoadGameStart = cm;
9207                 break;
9208               case XBoardGame:
9209                 lastLoadGameStart = cm; /* game counted already */
9210                 break;
9211               default:
9212                 /* impossible */
9213                 break;
9214             }
9215             if (gn > 0) {
9216                 do {
9217                     yyboardindex = forwardMostMove;
9218                     cm = (ChessMove) yylex();
9219                 } while (cm == PGNTag || cm == Comment);
9220             }
9221             break;
9222
9223           case WhiteWins:
9224           case BlackWins:
9225           case GameIsDrawn:
9226             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9227                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9228                     != CMAIL_OLD_RESULT) {
9229                     nCmailResults ++ ;
9230                     cmailResult[  CMAIL_MAX_GAMES
9231                                 - gn - 1] = CMAIL_OLD_RESULT;
9232                 }
9233             }
9234             break;
9235
9236           case NormalMove:
9237             /* Only a NormalMove can be at the start of a game
9238              * without a position diagram. */
9239             if (lastLoadGameStart == (ChessMove) 0) {
9240               gn--;
9241               lastLoadGameStart = MoveNumberOne;
9242             }
9243             break;
9244
9245           default:
9246             break;
9247         }
9248     }
9249     
9250     if (appData.debugMode)
9251       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9252
9253     if (cm == XBoardGame) {
9254         /* Skip any header junk before position diagram and/or move 1 */
9255         for (;;) {
9256             yyboardindex = forwardMostMove;
9257             cm = (ChessMove) yylex();
9258
9259             if (cm == (ChessMove) 0 ||
9260                 cm == GNUChessGame || cm == XBoardGame) {
9261                 /* Empty game; pretend end-of-file and handle later */
9262                 cm = (ChessMove) 0;
9263                 break;
9264             }
9265
9266             if (cm == MoveNumberOne || cm == PositionDiagram ||
9267                 cm == PGNTag || cm == Comment)
9268               break;
9269         }
9270     } else if (cm == GNUChessGame) {
9271         if (gameInfo.event != NULL) {
9272             free(gameInfo.event);
9273         }
9274         gameInfo.event = StrSave(yy_text);
9275     }   
9276
9277     startedFromSetupPosition = FALSE;
9278     while (cm == PGNTag) {
9279         if (appData.debugMode) 
9280           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9281         err = ParsePGNTag(yy_text, &gameInfo);
9282         if (!err) numPGNTags++;
9283
9284         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9285         if(gameInfo.variant != oldVariant) {
9286             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9287             InitPosition(TRUE);
9288             oldVariant = gameInfo.variant;
9289             if (appData.debugMode) 
9290               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9291         }
9292
9293
9294         if (gameInfo.fen != NULL) {
9295           Board initial_position;
9296           startedFromSetupPosition = TRUE;
9297           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9298             Reset(TRUE, TRUE);
9299             DisplayError(_("Bad FEN position in file"), 0);
9300             return FALSE;
9301           }
9302           CopyBoard(boards[0], initial_position);
9303           if (blackPlaysFirst) {
9304             currentMove = forwardMostMove = backwardMostMove = 1;
9305             CopyBoard(boards[1], initial_position);
9306             strcpy(moveList[0], "");
9307             strcpy(parseList[0], "");
9308             timeRemaining[0][1] = whiteTimeRemaining;
9309             timeRemaining[1][1] = blackTimeRemaining;
9310             if (commentList[0] != NULL) {
9311               commentList[1] = commentList[0];
9312               commentList[0] = NULL;
9313             }
9314           } else {
9315             currentMove = forwardMostMove = backwardMostMove = 0;
9316           }
9317           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9318           {   int i;
9319               initialRulePlies = FENrulePlies;
9320               epStatus[forwardMostMove] = FENepStatus;
9321               for( i=0; i< nrCastlingRights; i++ )
9322                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9323           }
9324           yyboardindex = forwardMostMove;
9325           free(gameInfo.fen);
9326           gameInfo.fen = NULL;
9327         }
9328
9329         yyboardindex = forwardMostMove;
9330         cm = (ChessMove) yylex();
9331
9332         /* Handle comments interspersed among the tags */
9333         while (cm == Comment) {
9334             char *p;
9335             if (appData.debugMode) 
9336               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9337             p = yy_text;
9338             if (*p == '{' || *p == '[' || *p == '(') {
9339                 p[strlen(p) - 1] = NULLCHAR;
9340                 p++;
9341             }
9342             while (*p == '\n') p++;
9343             AppendComment(currentMove, p);
9344             yyboardindex = forwardMostMove;
9345             cm = (ChessMove) yylex();
9346         }
9347     }
9348
9349     /* don't rely on existence of Event tag since if game was
9350      * pasted from clipboard the Event tag may not exist
9351      */
9352     if (numPGNTags > 0){
9353         char *tags;
9354         if (gameInfo.variant == VariantNormal) {
9355           gameInfo.variant = StringToVariant(gameInfo.event);
9356         }
9357         if (!matchMode) {
9358           if( appData.autoDisplayTags ) {
9359             tags = PGNTags(&gameInfo);
9360             TagsPopUp(tags, CmailMsg());
9361             free(tags);
9362           }
9363         }
9364     } else {
9365         /* Make something up, but don't display it now */
9366         SetGameInfo();
9367         TagsPopDown();
9368     }
9369
9370     if (cm == PositionDiagram) {
9371         int i, j;
9372         char *p;
9373         Board initial_position;
9374
9375         if (appData.debugMode)
9376           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9377
9378         if (!startedFromSetupPosition) {
9379             p = yy_text;
9380             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9381               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9382                 switch (*p) {
9383                   case '[':
9384                   case '-':
9385                   case ' ':
9386                   case '\t':
9387                   case '\n':
9388                   case '\r':
9389                     break;
9390                   default:
9391                     initial_position[i][j++] = CharToPiece(*p);
9392                     break;
9393                 }
9394             while (*p == ' ' || *p == '\t' ||
9395                    *p == '\n' || *p == '\r') p++;
9396         
9397             if (strncmp(p, "black", strlen("black"))==0)
9398               blackPlaysFirst = TRUE;
9399             else
9400               blackPlaysFirst = FALSE;
9401             startedFromSetupPosition = TRUE;
9402         
9403             CopyBoard(boards[0], initial_position);
9404             if (blackPlaysFirst) {
9405                 currentMove = forwardMostMove = backwardMostMove = 1;
9406                 CopyBoard(boards[1], initial_position);
9407                 strcpy(moveList[0], "");
9408                 strcpy(parseList[0], "");
9409                 timeRemaining[0][1] = whiteTimeRemaining;
9410                 timeRemaining[1][1] = blackTimeRemaining;
9411                 if (commentList[0] != NULL) {
9412                     commentList[1] = commentList[0];
9413                     commentList[0] = NULL;
9414                 }
9415             } else {
9416                 currentMove = forwardMostMove = backwardMostMove = 0;
9417             }
9418         }
9419         yyboardindex = forwardMostMove;
9420         cm = (ChessMove) yylex();
9421     }
9422
9423     if (first.pr == NoProc) {
9424         StartChessProgram(&first);
9425     }
9426     InitChessProgram(&first, FALSE);
9427     SendToProgram("force\n", &first);
9428     if (startedFromSetupPosition) {
9429         SendBoard(&first, forwardMostMove);
9430     if (appData.debugMode) {
9431         fprintf(debugFP, "Load Game\n");
9432     }
9433         DisplayBothClocks();
9434     }      
9435
9436     /* [HGM] server: flag to write setup moves in broadcast file as one */
9437     loadFlag = appData.suppressLoadMoves;
9438
9439     while (cm == Comment) {
9440         char *p;
9441         if (appData.debugMode) 
9442           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9443         p = yy_text;
9444         if (*p == '{' || *p == '[' || *p == '(') {
9445             p[strlen(p) - 1] = NULLCHAR;
9446             p++;
9447         }
9448         while (*p == '\n') p++;
9449         AppendComment(currentMove, p);
9450         yyboardindex = forwardMostMove;
9451         cm = (ChessMove) yylex();
9452     }
9453
9454     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9455         cm == WhiteWins || cm == BlackWins ||
9456         cm == GameIsDrawn || cm == GameUnfinished) {
9457         DisplayMessage("", _("No moves in game"));
9458         if (cmailMsgLoaded) {
9459             if (appData.debugMode)
9460               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9461             ClearHighlights();
9462             flipView = FALSE;
9463         }
9464         DrawPosition(FALSE, boards[currentMove]);
9465         DisplayBothClocks();
9466         gameMode = EditGame;
9467         ModeHighlight();
9468         gameFileFP = NULL;
9469         cmailOldMove = 0;
9470         return TRUE;
9471     }
9472
9473     // [HGM] PV info: routine tests if comment empty
9474     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9475         DisplayComment(currentMove - 1, commentList[currentMove]);
9476     }
9477     if (!matchMode && appData.timeDelay != 0) 
9478       DrawPosition(FALSE, boards[currentMove]);
9479
9480     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9481       programStats.ok_to_send = 1;
9482     }
9483
9484     /* if the first token after the PGN tags is a move
9485      * and not move number 1, retrieve it from the parser 
9486      */
9487     if (cm != MoveNumberOne)
9488         LoadGameOneMove(cm);
9489
9490     /* load the remaining moves from the file */
9491     while (LoadGameOneMove((ChessMove)0)) {
9492       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9493       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9494     }
9495
9496     /* rewind to the start of the game */
9497     currentMove = backwardMostMove;
9498
9499     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9500
9501     if (oldGameMode == AnalyzeFile ||
9502         oldGameMode == AnalyzeMode) {
9503       AnalyzeFileEvent();
9504     }
9505
9506     if (matchMode || appData.timeDelay == 0) {
9507       ToEndEvent();
9508       gameMode = EditGame;
9509       ModeHighlight();
9510     } else if (appData.timeDelay > 0) {
9511       AutoPlayGameLoop();
9512     }
9513
9514     if (appData.debugMode) 
9515         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9516
9517     loadFlag = 0; /* [HGM] true game starts */
9518     return TRUE;
9519 }
9520
9521 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9522 int
9523 ReloadPosition(offset)
9524      int offset;
9525 {
9526     int positionNumber = lastLoadPositionNumber + offset;
9527     if (lastLoadPositionFP == NULL) {
9528         DisplayError(_("No position has been loaded yet"), 0);
9529         return FALSE;
9530     }
9531     if (positionNumber <= 0) {
9532         DisplayError(_("Can't back up any further"), 0);
9533         return FALSE;
9534     }
9535     return LoadPosition(lastLoadPositionFP, positionNumber,
9536                         lastLoadPositionTitle);
9537 }
9538
9539 /* Load the nth position from the given file */
9540 int
9541 LoadPositionFromFile(filename, n, title)
9542      char *filename;
9543      int n;
9544      char *title;
9545 {
9546     FILE *f;
9547     char buf[MSG_SIZ];
9548
9549     if (strcmp(filename, "-") == 0) {
9550         return LoadPosition(stdin, n, "stdin");
9551     } else {
9552         f = fopen(filename, "rb");
9553         if (f == NULL) {
9554             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9555             DisplayError(buf, errno);
9556             return FALSE;
9557         } else {
9558             return LoadPosition(f, n, title);
9559         }
9560     }
9561 }
9562
9563 /* Load the nth position from the given open file, and close it */
9564 int
9565 LoadPosition(f, positionNumber, title)
9566      FILE *f;
9567      int positionNumber;
9568      char *title;
9569 {
9570     char *p, line[MSG_SIZ];
9571     Board initial_position;
9572     int i, j, fenMode, pn;
9573     
9574     if (gameMode == Training )
9575         SetTrainingModeOff();
9576
9577     if (gameMode != BeginningOfGame) {
9578         Reset(FALSE, TRUE);
9579     }
9580     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9581         fclose(lastLoadPositionFP);
9582     }
9583     if (positionNumber == 0) positionNumber = 1;
9584     lastLoadPositionFP = f;
9585     lastLoadPositionNumber = positionNumber;
9586     strcpy(lastLoadPositionTitle, title);
9587     if (first.pr == NoProc) {
9588       StartChessProgram(&first);
9589       InitChessProgram(&first, FALSE);
9590     }    
9591     pn = positionNumber;
9592     if (positionNumber < 0) {
9593         /* Negative position number means to seek to that byte offset */
9594         if (fseek(f, -positionNumber, 0) == -1) {
9595             DisplayError(_("Can't seek on position file"), 0);
9596             return FALSE;
9597         };
9598         pn = 1;
9599     } else {
9600         if (fseek(f, 0, 0) == -1) {
9601             if (f == lastLoadPositionFP ?
9602                 positionNumber == lastLoadPositionNumber + 1 :
9603                 positionNumber == 1) {
9604                 pn = 1;
9605             } else {
9606                 DisplayError(_("Can't seek on position file"), 0);
9607                 return FALSE;
9608             }
9609         }
9610     }
9611     /* See if this file is FEN or old-style xboard */
9612     if (fgets(line, MSG_SIZ, f) == NULL) {
9613         DisplayError(_("Position not found in file"), 0);
9614         return FALSE;
9615     }
9616     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9617     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9618
9619     if (pn >= 2) {
9620         if (fenMode || line[0] == '#') pn--;
9621         while (pn > 0) {
9622             /* skip positions before number pn */
9623             if (fgets(line, MSG_SIZ, f) == NULL) {
9624                 Reset(TRUE, TRUE);
9625                 DisplayError(_("Position not found in file"), 0);
9626                 return FALSE;
9627             }
9628             if (fenMode || line[0] == '#') pn--;
9629         }
9630     }
9631
9632     if (fenMode) {
9633         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9634             DisplayError(_("Bad FEN position in file"), 0);
9635             return FALSE;
9636         }
9637     } else {
9638         (void) fgets(line, MSG_SIZ, f);
9639         (void) fgets(line, MSG_SIZ, f);
9640     
9641         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9642             (void) fgets(line, MSG_SIZ, f);
9643             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9644                 if (*p == ' ')
9645                   continue;
9646                 initial_position[i][j++] = CharToPiece(*p);
9647             }
9648         }
9649     
9650         blackPlaysFirst = FALSE;
9651         if (!feof(f)) {
9652             (void) fgets(line, MSG_SIZ, f);
9653             if (strncmp(line, "black", strlen("black"))==0)
9654               blackPlaysFirst = TRUE;
9655         }
9656     }
9657     startedFromSetupPosition = TRUE;
9658     
9659     SendToProgram("force\n", &first);
9660     CopyBoard(boards[0], initial_position);
9661     if (blackPlaysFirst) {
9662         currentMove = forwardMostMove = backwardMostMove = 1;
9663         strcpy(moveList[0], "");
9664         strcpy(parseList[0], "");
9665         CopyBoard(boards[1], initial_position);
9666         DisplayMessage("", _("Black to play"));
9667     } else {
9668         currentMove = forwardMostMove = backwardMostMove = 0;
9669         DisplayMessage("", _("White to play"));
9670     }
9671           /* [HGM] copy FEN attributes as well */
9672           {   int i;
9673               initialRulePlies = FENrulePlies;
9674               epStatus[forwardMostMove] = FENepStatus;
9675               for( i=0; i< nrCastlingRights; i++ )
9676                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9677           }
9678     SendBoard(&first, forwardMostMove);
9679     if (appData.debugMode) {
9680 int i, j;
9681   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9682   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9683         fprintf(debugFP, "Load Position\n");
9684     }
9685
9686     if (positionNumber > 1) {
9687         sprintf(line, "%s %d", title, positionNumber);
9688         DisplayTitle(line);
9689     } else {
9690         DisplayTitle(title);
9691     }
9692     gameMode = EditGame;
9693     ModeHighlight();
9694     ResetClocks();
9695     timeRemaining[0][1] = whiteTimeRemaining;
9696     timeRemaining[1][1] = blackTimeRemaining;
9697     DrawPosition(FALSE, boards[currentMove]);
9698    
9699     return TRUE;
9700 }
9701
9702
9703 void
9704 CopyPlayerNameIntoFileName(dest, src)
9705      char **dest, *src;
9706 {
9707     while (*src != NULLCHAR && *src != ',') {
9708         if (*src == ' ') {
9709             *(*dest)++ = '_';
9710             src++;
9711         } else {
9712             *(*dest)++ = *src++;
9713         }
9714     }
9715 }
9716
9717 char *DefaultFileName(ext)
9718      char *ext;
9719 {
9720     static char def[MSG_SIZ];
9721     char *p;
9722
9723     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9724         p = def;
9725         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9726         *p++ = '-';
9727         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9728         *p++ = '.';
9729         strcpy(p, ext);
9730     } else {
9731         def[0] = NULLCHAR;
9732     }
9733     return def;
9734 }
9735
9736 /* Save the current game to the given file */
9737 int
9738 SaveGameToFile(filename, append)
9739      char *filename;
9740      int append;
9741 {
9742     FILE *f;
9743     char buf[MSG_SIZ];
9744
9745     if (strcmp(filename, "-") == 0) {
9746         return SaveGame(stdout, 0, NULL);
9747     } else {
9748         f = fopen(filename, append ? "a" : "w");
9749         if (f == NULL) {
9750             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9751             DisplayError(buf, errno);
9752             return FALSE;
9753         } else {
9754             return SaveGame(f, 0, NULL);
9755         }
9756     }
9757 }
9758
9759 char *
9760 SavePart(str)
9761      char *str;
9762 {
9763     static char buf[MSG_SIZ];
9764     char *p;
9765     
9766     p = strchr(str, ' ');
9767     if (p == NULL) return str;
9768     strncpy(buf, str, p - str);
9769     buf[p - str] = NULLCHAR;
9770     return buf;
9771 }
9772
9773 #define PGN_MAX_LINE 75
9774
9775 #define PGN_SIDE_WHITE  0
9776 #define PGN_SIDE_BLACK  1
9777
9778 /* [AS] */
9779 static int FindFirstMoveOutOfBook( int side )
9780 {
9781     int result = -1;
9782
9783     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9784         int index = backwardMostMove;
9785         int has_book_hit = 0;
9786
9787         if( (index % 2) != side ) {
9788             index++;
9789         }
9790
9791         while( index < forwardMostMove ) {
9792             /* Check to see if engine is in book */
9793             int depth = pvInfoList[index].depth;
9794             int score = pvInfoList[index].score;
9795             int in_book = 0;
9796
9797             if( depth <= 2 ) {
9798                 in_book = 1;
9799             }
9800             else if( score == 0 && depth == 63 ) {
9801                 in_book = 1; /* Zappa */
9802             }
9803             else if( score == 2 && depth == 99 ) {
9804                 in_book = 1; /* Abrok */
9805             }
9806
9807             has_book_hit += in_book;
9808
9809             if( ! in_book ) {
9810                 result = index;
9811
9812                 break;
9813             }
9814
9815             index += 2;
9816         }
9817     }
9818
9819     return result;
9820 }
9821
9822 /* [AS] */
9823 void GetOutOfBookInfo( char * buf )
9824 {
9825     int oob[2];
9826     int i;
9827     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9828
9829     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9830     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9831
9832     *buf = '\0';
9833
9834     if( oob[0] >= 0 || oob[1] >= 0 ) {
9835         for( i=0; i<2; i++ ) {
9836             int idx = oob[i];
9837
9838             if( idx >= 0 ) {
9839                 if( i > 0 && oob[0] >= 0 ) {
9840                     strcat( buf, "   " );
9841                 }
9842
9843                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9844                 sprintf( buf+strlen(buf), "%s%.2f", 
9845                     pvInfoList[idx].score >= 0 ? "+" : "",
9846                     pvInfoList[idx].score / 100.0 );
9847             }
9848         }
9849     }
9850 }
9851
9852 /* Save game in PGN style and close the file */
9853 int
9854 SaveGamePGN(f)
9855      FILE *f;
9856 {
9857     int i, offset, linelen, newblock;
9858     time_t tm;
9859 //    char *movetext;
9860     char numtext[32];
9861     int movelen, numlen, blank;
9862     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9863
9864     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9865     
9866     tm = time((time_t *) NULL);
9867     
9868     PrintPGNTags(f, &gameInfo);
9869     
9870     if (backwardMostMove > 0 || startedFromSetupPosition) {
9871         char *fen = PositionToFEN(backwardMostMove, NULL);
9872         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9873         fprintf(f, "\n{--------------\n");
9874         PrintPosition(f, backwardMostMove);
9875         fprintf(f, "--------------}\n");
9876         free(fen);
9877     }
9878     else {
9879         /* [AS] Out of book annotation */
9880         if( appData.saveOutOfBookInfo ) {
9881             char buf[64];
9882
9883             GetOutOfBookInfo( buf );
9884
9885             if( buf[0] != '\0' ) {
9886                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9887             }
9888         }
9889
9890         fprintf(f, "\n");
9891     }
9892
9893     i = backwardMostMove;
9894     linelen = 0;
9895     newblock = TRUE;
9896
9897     while (i < forwardMostMove) {
9898         /* Print comments preceding this move */
9899         if (commentList[i] != NULL) {
9900             if (linelen > 0) fprintf(f, "\n");
9901             fprintf(f, "{\n%s}\n", commentList[i]);
9902             linelen = 0;
9903             newblock = TRUE;
9904         }
9905
9906         /* Format move number */
9907         if ((i % 2) == 0) {
9908             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9909         } else {
9910             if (newblock) {
9911                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9912             } else {
9913                 numtext[0] = NULLCHAR;
9914             }
9915         }
9916         numlen = strlen(numtext);
9917         newblock = FALSE;
9918
9919         /* Print move number */
9920         blank = linelen > 0 && numlen > 0;
9921         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9922             fprintf(f, "\n");
9923             linelen = 0;
9924             blank = 0;
9925         }
9926         if (blank) {
9927             fprintf(f, " ");
9928             linelen++;
9929         }
9930         fprintf(f, "%s", numtext);
9931         linelen += numlen;
9932
9933         /* Get move */
9934         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9935         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9936
9937         /* Print move */
9938         blank = linelen > 0 && movelen > 0;
9939         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9940             fprintf(f, "\n");
9941             linelen = 0;
9942             blank = 0;
9943         }
9944         if (blank) {
9945             fprintf(f, " ");
9946             linelen++;
9947         }
9948         fprintf(f, "%s", move_buffer);
9949         linelen += movelen;
9950
9951         /* [AS] Add PV info if present */
9952         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9953             /* [HGM] add time */
9954             char buf[MSG_SIZ]; int seconds = 0;
9955
9956             if(i >= backwardMostMove) {
9957                 if(WhiteOnMove(i))
9958                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9959                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9960                 else
9961                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9962                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9963             }
9964             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9965
9966             if( seconds <= 0) buf[0] = 0; else
9967             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9968                 seconds = (seconds + 4)/10; // round to full seconds
9969                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9970                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9971             }
9972
9973             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9974                 pvInfoList[i].score >= 0 ? "+" : "",
9975                 pvInfoList[i].score / 100.0,
9976                 pvInfoList[i].depth,
9977                 buf );
9978
9979             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9980
9981             /* Print score/depth */
9982             blank = linelen > 0 && movelen > 0;
9983             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9984                 fprintf(f, "\n");
9985                 linelen = 0;
9986                 blank = 0;
9987             }
9988             if (blank) {
9989                 fprintf(f, " ");
9990                 linelen++;
9991             }
9992             fprintf(f, "%s", move_buffer);
9993             linelen += movelen;
9994         }
9995
9996         i++;
9997     }
9998     
9999     /* Start a new line */
10000     if (linelen > 0) fprintf(f, "\n");
10001
10002     /* Print comments after last move */
10003     if (commentList[i] != NULL) {
10004         fprintf(f, "{\n%s}\n", commentList[i]);
10005     }
10006
10007     /* Print result */
10008     if (gameInfo.resultDetails != NULL &&
10009         gameInfo.resultDetails[0] != NULLCHAR) {
10010         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10011                 PGNResult(gameInfo.result));
10012     } else {
10013         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10014     }
10015
10016     fclose(f);
10017     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10018     return TRUE;
10019 }
10020
10021 /* Save game in old style and close the file */
10022 int
10023 SaveGameOldStyle(f)
10024      FILE *f;
10025 {
10026     int i, offset;
10027     time_t tm;
10028     
10029     tm = time((time_t *) NULL);
10030     
10031     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10032     PrintOpponents(f);
10033     
10034     if (backwardMostMove > 0 || startedFromSetupPosition) {
10035         fprintf(f, "\n[--------------\n");
10036         PrintPosition(f, backwardMostMove);
10037         fprintf(f, "--------------]\n");
10038     } else {
10039         fprintf(f, "\n");
10040     }
10041
10042     i = backwardMostMove;
10043     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10044
10045     while (i < forwardMostMove) {
10046         if (commentList[i] != NULL) {
10047             fprintf(f, "[%s]\n", commentList[i]);
10048         }
10049
10050         if ((i % 2) == 1) {
10051             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10052             i++;
10053         } else {
10054             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10055             i++;
10056             if (commentList[i] != NULL) {
10057                 fprintf(f, "\n");
10058                 continue;
10059             }
10060             if (i >= forwardMostMove) {
10061                 fprintf(f, "\n");
10062                 break;
10063             }
10064             fprintf(f, "%s\n", parseList[i]);
10065             i++;
10066         }
10067     }
10068     
10069     if (commentList[i] != NULL) {
10070         fprintf(f, "[%s]\n", commentList[i]);
10071     }
10072
10073     /* This isn't really the old style, but it's close enough */
10074     if (gameInfo.resultDetails != NULL &&
10075         gameInfo.resultDetails[0] != NULLCHAR) {
10076         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10077                 gameInfo.resultDetails);
10078     } else {
10079         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10080     }
10081
10082     fclose(f);
10083     return TRUE;
10084 }
10085
10086 /* Save the current game to open file f and close the file */
10087 int
10088 SaveGame(f, dummy, dummy2)
10089      FILE *f;
10090      int dummy;
10091      char *dummy2;
10092 {
10093     if (gameMode == EditPosition) EditPositionDone();
10094     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10095     if (appData.oldSaveStyle)
10096       return SaveGameOldStyle(f);
10097     else
10098       return SaveGamePGN(f);
10099 }
10100
10101 /* Save the current position to the given file */
10102 int
10103 SavePositionToFile(filename)
10104      char *filename;
10105 {
10106     FILE *f;
10107     char buf[MSG_SIZ];
10108
10109     if (strcmp(filename, "-") == 0) {
10110         return SavePosition(stdout, 0, NULL);
10111     } else {
10112         f = fopen(filename, "a");
10113         if (f == NULL) {
10114             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10115             DisplayError(buf, errno);
10116             return FALSE;
10117         } else {
10118             SavePosition(f, 0, NULL);
10119             return TRUE;
10120         }
10121     }
10122 }
10123
10124 /* Save the current position to the given open file and close the file */
10125 int
10126 SavePosition(f, dummy, dummy2)
10127      FILE *f;
10128      int dummy;
10129      char *dummy2;
10130 {
10131     time_t tm;
10132     char *fen;
10133     
10134     if (appData.oldSaveStyle) {
10135         tm = time((time_t *) NULL);
10136     
10137         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10138         PrintOpponents(f);
10139         fprintf(f, "[--------------\n");
10140         PrintPosition(f, currentMove);
10141         fprintf(f, "--------------]\n");
10142     } else {
10143         fen = PositionToFEN(currentMove, NULL);
10144         fprintf(f, "%s\n", fen);
10145         free(fen);
10146     }
10147     fclose(f);
10148     return TRUE;
10149 }
10150
10151 void
10152 ReloadCmailMsgEvent(unregister)
10153      int unregister;
10154 {
10155 #if !WIN32
10156     static char *inFilename = NULL;
10157     static char *outFilename;
10158     int i;
10159     struct stat inbuf, outbuf;
10160     int status;
10161     
10162     /* Any registered moves are unregistered if unregister is set, */
10163     /* i.e. invoked by the signal handler */
10164     if (unregister) {
10165         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10166             cmailMoveRegistered[i] = FALSE;
10167             if (cmailCommentList[i] != NULL) {
10168                 free(cmailCommentList[i]);
10169                 cmailCommentList[i] = NULL;
10170             }
10171         }
10172         nCmailMovesRegistered = 0;
10173     }
10174
10175     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10176         cmailResult[i] = CMAIL_NOT_RESULT;
10177     }
10178     nCmailResults = 0;
10179
10180     if (inFilename == NULL) {
10181         /* Because the filenames are static they only get malloced once  */
10182         /* and they never get freed                                      */
10183         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10184         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10185
10186         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10187         sprintf(outFilename, "%s.out", appData.cmailGameName);
10188     }
10189     
10190     status = stat(outFilename, &outbuf);
10191     if (status < 0) {
10192         cmailMailedMove = FALSE;
10193     } else {
10194         status = stat(inFilename, &inbuf);
10195         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10196     }
10197     
10198     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10199        counts the games, notes how each one terminated, etc.
10200        
10201        It would be nice to remove this kludge and instead gather all
10202        the information while building the game list.  (And to keep it
10203        in the game list nodes instead of having a bunch of fixed-size
10204        parallel arrays.)  Note this will require getting each game's
10205        termination from the PGN tags, as the game list builder does
10206        not process the game moves.  --mann
10207        */
10208     cmailMsgLoaded = TRUE;
10209     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10210     
10211     /* Load first game in the file or popup game menu */
10212     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10213
10214 #endif /* !WIN32 */
10215     return;
10216 }
10217
10218 int
10219 RegisterMove()
10220 {
10221     FILE *f;
10222     char string[MSG_SIZ];
10223
10224     if (   cmailMailedMove
10225         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10226         return TRUE;            /* Allow free viewing  */
10227     }
10228
10229     /* Unregister move to ensure that we don't leave RegisterMove        */
10230     /* with the move registered when the conditions for registering no   */
10231     /* longer hold                                                       */
10232     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10233         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10234         nCmailMovesRegistered --;
10235
10236         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10237           {
10238               free(cmailCommentList[lastLoadGameNumber - 1]);
10239               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10240           }
10241     }
10242
10243     if (cmailOldMove == -1) {
10244         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10245         return FALSE;
10246     }
10247
10248     if (currentMove > cmailOldMove + 1) {
10249         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10250         return FALSE;
10251     }
10252
10253     if (currentMove < cmailOldMove) {
10254         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10255         return FALSE;
10256     }
10257
10258     if (forwardMostMove > currentMove) {
10259         /* Silently truncate extra moves */
10260         TruncateGame();
10261     }
10262
10263     if (   (currentMove == cmailOldMove + 1)
10264         || (   (currentMove == cmailOldMove)
10265             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10266                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10267         if (gameInfo.result != GameUnfinished) {
10268             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10269         }
10270
10271         if (commentList[currentMove] != NULL) {
10272             cmailCommentList[lastLoadGameNumber - 1]
10273               = StrSave(commentList[currentMove]);
10274         }
10275         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10276
10277         if (appData.debugMode)
10278           fprintf(debugFP, "Saving %s for game %d\n",
10279                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10280
10281         sprintf(string,
10282                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10283         
10284         f = fopen(string, "w");
10285         if (appData.oldSaveStyle) {
10286             SaveGameOldStyle(f); /* also closes the file */
10287             
10288             sprintf(string, "%s.pos.out", appData.cmailGameName);
10289             f = fopen(string, "w");
10290             SavePosition(f, 0, NULL); /* also closes the file */
10291         } else {
10292             fprintf(f, "{--------------\n");
10293             PrintPosition(f, currentMove);
10294             fprintf(f, "--------------}\n\n");
10295             
10296             SaveGame(f, 0, NULL); /* also closes the file*/
10297         }
10298         
10299         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10300         nCmailMovesRegistered ++;
10301     } else if (nCmailGames == 1) {
10302         DisplayError(_("You have not made a move yet"), 0);
10303         return FALSE;
10304     }
10305
10306     return TRUE;
10307 }
10308
10309 void
10310 MailMoveEvent()
10311 {
10312 #if !WIN32
10313     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10314     FILE *commandOutput;
10315     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10316     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10317     int nBuffers;
10318     int i;
10319     int archived;
10320     char *arcDir;
10321
10322     if (! cmailMsgLoaded) {
10323         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10324         return;
10325     }
10326
10327     if (nCmailGames == nCmailResults) {
10328         DisplayError(_("No unfinished games"), 0);
10329         return;
10330     }
10331
10332 #if CMAIL_PROHIBIT_REMAIL
10333     if (cmailMailedMove) {
10334         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);
10335         DisplayError(msg, 0);
10336         return;
10337     }
10338 #endif
10339
10340     if (! (cmailMailedMove || RegisterMove())) return;
10341     
10342     if (   cmailMailedMove
10343         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10344         sprintf(string, partCommandString,
10345                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10346         commandOutput = popen(string, "r");
10347
10348         if (commandOutput == NULL) {
10349             DisplayError(_("Failed to invoke cmail"), 0);
10350         } else {
10351             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10352                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10353             }
10354             if (nBuffers > 1) {
10355                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10356                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10357                 nBytes = MSG_SIZ - 1;
10358             } else {
10359                 (void) memcpy(msg, buffer, nBytes);
10360             }
10361             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10362
10363             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10364                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10365
10366                 archived = TRUE;
10367                 for (i = 0; i < nCmailGames; i ++) {
10368                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10369                         archived = FALSE;
10370                     }
10371                 }
10372                 if (   archived
10373                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10374                         != NULL)) {
10375                     sprintf(buffer, "%s/%s.%s.archive",
10376                             arcDir,
10377                             appData.cmailGameName,
10378                             gameInfo.date);
10379                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10380                     cmailMsgLoaded = FALSE;
10381                 }
10382             }
10383
10384             DisplayInformation(msg);
10385             pclose(commandOutput);
10386         }
10387     } else {
10388         if ((*cmailMsg) != '\0') {
10389             DisplayInformation(cmailMsg);
10390         }
10391     }
10392
10393     return;
10394 #endif /* !WIN32 */
10395 }
10396
10397 char *
10398 CmailMsg()
10399 {
10400 #if WIN32
10401     return NULL;
10402 #else
10403     int  prependComma = 0;
10404     char number[5];
10405     char string[MSG_SIZ];       /* Space for game-list */
10406     int  i;
10407     
10408     if (!cmailMsgLoaded) return "";
10409
10410     if (cmailMailedMove) {
10411         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10412     } else {
10413         /* Create a list of games left */
10414         sprintf(string, "[");
10415         for (i = 0; i < nCmailGames; i ++) {
10416             if (! (   cmailMoveRegistered[i]
10417                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10418                 if (prependComma) {
10419                     sprintf(number, ",%d", i + 1);
10420                 } else {
10421                     sprintf(number, "%d", i + 1);
10422                     prependComma = 1;
10423                 }
10424                 
10425                 strcat(string, number);
10426             }
10427         }
10428         strcat(string, "]");
10429
10430         if (nCmailMovesRegistered + nCmailResults == 0) {
10431             switch (nCmailGames) {
10432               case 1:
10433                 sprintf(cmailMsg,
10434                         _("Still need to make move for game\n"));
10435                 break;
10436                 
10437               case 2:
10438                 sprintf(cmailMsg,
10439                         _("Still need to make moves for both games\n"));
10440                 break;
10441                 
10442               default:
10443                 sprintf(cmailMsg,
10444                         _("Still need to make moves for all %d games\n"),
10445                         nCmailGames);
10446                 break;
10447             }
10448         } else {
10449             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10450               case 1:
10451                 sprintf(cmailMsg,
10452                         _("Still need to make a move for game %s\n"),
10453                         string);
10454                 break;
10455                 
10456               case 0:
10457                 if (nCmailResults == nCmailGames) {
10458                     sprintf(cmailMsg, _("No unfinished games\n"));
10459                 } else {
10460                     sprintf(cmailMsg, _("Ready to send mail\n"));
10461                 }
10462                 break;
10463                 
10464               default:
10465                 sprintf(cmailMsg,
10466                         _("Still need to make moves for games %s\n"),
10467                         string);
10468             }
10469         }
10470     }
10471     return cmailMsg;
10472 #endif /* WIN32 */
10473 }
10474
10475 void
10476 ResetGameEvent()
10477 {
10478     if (gameMode == Training)
10479       SetTrainingModeOff();
10480
10481     Reset(TRUE, TRUE);
10482     cmailMsgLoaded = FALSE;
10483     if (appData.icsActive) {
10484       SendToICS(ics_prefix);
10485       SendToICS("refresh\n");
10486     }
10487 }
10488
10489 void
10490 ExitEvent(status)
10491      int status;
10492 {
10493     exiting++;
10494     if (exiting > 2) {
10495       /* Give up on clean exit */
10496       exit(status);
10497     }
10498     if (exiting > 1) {
10499       /* Keep trying for clean exit */
10500       return;
10501     }
10502
10503     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10504
10505     if (telnetISR != NULL) {
10506       RemoveInputSource(telnetISR);
10507     }
10508     if (icsPR != NoProc) {
10509       DestroyChildProcess(icsPR, TRUE);
10510     }
10511
10512     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10513     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10514
10515     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10516     /* make sure this other one finishes before killing it!                  */
10517     if(endingGame) { int count = 0;
10518         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10519         while(endingGame && count++ < 10) DoSleep(1);
10520         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10521     }
10522
10523     /* Kill off chess programs */
10524     if (first.pr != NoProc) {
10525         ExitAnalyzeMode();
10526         
10527         DoSleep( appData.delayBeforeQuit );
10528         SendToProgram("quit\n", &first);
10529         DoSleep( appData.delayAfterQuit );
10530         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10531     }
10532     if (second.pr != NoProc) {
10533         DoSleep( appData.delayBeforeQuit );
10534         SendToProgram("quit\n", &second);
10535         DoSleep( appData.delayAfterQuit );
10536         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10537     }
10538     if (first.isr != NULL) {
10539         RemoveInputSource(first.isr);
10540     }
10541     if (second.isr != NULL) {
10542         RemoveInputSource(second.isr);
10543     }
10544
10545     ShutDownFrontEnd();
10546     exit(status);
10547 }
10548
10549 void
10550 PauseEvent()
10551 {
10552     if (appData.debugMode)
10553         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10554     if (pausing) {
10555         pausing = FALSE;
10556         ModeHighlight();
10557         if (gameMode == MachinePlaysWhite ||
10558             gameMode == MachinePlaysBlack) {
10559             StartClocks();
10560         } else {
10561             DisplayBothClocks();
10562         }
10563         if (gameMode == PlayFromGameFile) {
10564             if (appData.timeDelay >= 0) 
10565                 AutoPlayGameLoop();
10566         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10567             Reset(FALSE, TRUE);
10568             SendToICS(ics_prefix);
10569             SendToICS("refresh\n");
10570         } else if (currentMove < forwardMostMove) {
10571             ForwardInner(forwardMostMove);
10572         }
10573         pauseExamInvalid = FALSE;
10574     } else {
10575         switch (gameMode) {
10576           default:
10577             return;
10578           case IcsExamining:
10579             pauseExamForwardMostMove = forwardMostMove;
10580             pauseExamInvalid = FALSE;
10581             /* fall through */
10582           case IcsObserving:
10583           case IcsPlayingWhite:
10584           case IcsPlayingBlack:
10585             pausing = TRUE;
10586             ModeHighlight();
10587             return;
10588           case PlayFromGameFile:
10589             (void) StopLoadGameTimer();
10590             pausing = TRUE;
10591             ModeHighlight();
10592             break;
10593           case BeginningOfGame:
10594             if (appData.icsActive) return;
10595             /* else fall through */
10596           case MachinePlaysWhite:
10597           case MachinePlaysBlack:
10598           case TwoMachinesPlay:
10599             if (forwardMostMove == 0)
10600               return;           /* don't pause if no one has moved */
10601             if ((gameMode == MachinePlaysWhite &&
10602                  !WhiteOnMove(forwardMostMove)) ||
10603                 (gameMode == MachinePlaysBlack &&
10604                  WhiteOnMove(forwardMostMove))) {
10605                 StopClocks();
10606             }
10607             pausing = TRUE;
10608             ModeHighlight();
10609             break;
10610         }
10611     }
10612 }
10613
10614 void
10615 EditCommentEvent()
10616 {
10617     char title[MSG_SIZ];
10618
10619     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10620         strcpy(title, _("Edit comment"));
10621     } else {
10622         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10623                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10624                 parseList[currentMove - 1]);
10625     }
10626
10627     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10628 }
10629
10630
10631 void
10632 EditTagsEvent()
10633 {
10634     char *tags = PGNTags(&gameInfo);
10635     EditTagsPopUp(tags);
10636     free(tags);
10637 }
10638
10639 void
10640 AnalyzeModeEvent()
10641 {
10642     if (appData.noChessProgram || gameMode == AnalyzeMode)
10643       return;
10644
10645     if (gameMode != AnalyzeFile) {
10646         if (!appData.icsEngineAnalyze) {
10647                EditGameEvent();
10648                if (gameMode != EditGame) return;
10649         }
10650         ResurrectChessProgram();
10651         SendToProgram("analyze\n", &first);
10652         first.analyzing = TRUE;
10653         /*first.maybeThinking = TRUE;*/
10654         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10655         EngineOutputPopUp();
10656     }
10657     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10658     pausing = FALSE;
10659     ModeHighlight();
10660     SetGameInfo();
10661
10662     StartAnalysisClock();
10663     GetTimeMark(&lastNodeCountTime);
10664     lastNodeCount = 0;
10665 }
10666
10667 void
10668 AnalyzeFileEvent()
10669 {
10670     if (appData.noChessProgram || gameMode == AnalyzeFile)
10671       return;
10672
10673     if (gameMode != AnalyzeMode) {
10674         EditGameEvent();
10675         if (gameMode != EditGame) return;
10676         ResurrectChessProgram();
10677         SendToProgram("analyze\n", &first);
10678         first.analyzing = TRUE;
10679         /*first.maybeThinking = TRUE;*/
10680         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10681         EngineOutputPopUp();
10682     }
10683     gameMode = AnalyzeFile;
10684     pausing = FALSE;
10685     ModeHighlight();
10686     SetGameInfo();
10687
10688     StartAnalysisClock();
10689     GetTimeMark(&lastNodeCountTime);
10690     lastNodeCount = 0;
10691 }
10692
10693 void
10694 MachineWhiteEvent()
10695 {
10696     char buf[MSG_SIZ];
10697     char *bookHit = NULL;
10698
10699     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10700       return;
10701
10702
10703     if (gameMode == PlayFromGameFile || 
10704         gameMode == TwoMachinesPlay  || 
10705         gameMode == Training         || 
10706         gameMode == AnalyzeMode      || 
10707         gameMode == EndOfGame)
10708         EditGameEvent();
10709
10710     if (gameMode == EditPosition) 
10711         EditPositionDone();
10712
10713     if (!WhiteOnMove(currentMove)) {
10714         DisplayError(_("It is not White's turn"), 0);
10715         return;
10716     }
10717   
10718     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10719       ExitAnalyzeMode();
10720
10721     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10722         gameMode == AnalyzeFile)
10723         TruncateGame();
10724
10725     ResurrectChessProgram();    /* in case it isn't running */
10726     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10727         gameMode = MachinePlaysWhite;
10728         ResetClocks();
10729     } else
10730     gameMode = MachinePlaysWhite;
10731     pausing = FALSE;
10732     ModeHighlight();
10733     SetGameInfo();
10734     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10735     DisplayTitle(buf);
10736     if (first.sendName) {
10737       sprintf(buf, "name %s\n", gameInfo.black);
10738       SendToProgram(buf, &first);
10739     }
10740     if (first.sendTime) {
10741       if (first.useColors) {
10742         SendToProgram("black\n", &first); /*gnu kludge*/
10743       }
10744       SendTimeRemaining(&first, TRUE);
10745     }
10746     if (first.useColors) {
10747       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10748     }
10749     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10750     SetMachineThinkingEnables();
10751     first.maybeThinking = TRUE;
10752     StartClocks();
10753     firstMove = FALSE;
10754
10755     if (appData.autoFlipView && !flipView) {
10756       flipView = !flipView;
10757       DrawPosition(FALSE, NULL);
10758       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10759     }
10760
10761     if(bookHit) { // [HGM] book: simulate book reply
10762         static char bookMove[MSG_SIZ]; // a bit generous?
10763
10764         programStats.nodes = programStats.depth = programStats.time = 
10765         programStats.score = programStats.got_only_move = 0;
10766         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10767
10768         strcpy(bookMove, "move ");
10769         strcat(bookMove, bookHit);
10770         HandleMachineMove(bookMove, &first);
10771     }
10772 }
10773
10774 void
10775 MachineBlackEvent()
10776 {
10777     char buf[MSG_SIZ];
10778    char *bookHit = NULL;
10779
10780     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10781         return;
10782
10783
10784     if (gameMode == PlayFromGameFile || 
10785         gameMode == TwoMachinesPlay  || 
10786         gameMode == Training         || 
10787         gameMode == AnalyzeMode      || 
10788         gameMode == EndOfGame)
10789         EditGameEvent();
10790
10791     if (gameMode == EditPosition) 
10792         EditPositionDone();
10793
10794     if (WhiteOnMove(currentMove)) {
10795         DisplayError(_("It is not Black's turn"), 0);
10796         return;
10797     }
10798     
10799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10800       ExitAnalyzeMode();
10801
10802     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10803         gameMode == AnalyzeFile)
10804         TruncateGame();
10805
10806     ResurrectChessProgram();    /* in case it isn't running */
10807     gameMode = MachinePlaysBlack;
10808     pausing = FALSE;
10809     ModeHighlight();
10810     SetGameInfo();
10811     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10812     DisplayTitle(buf);
10813     if (first.sendName) {
10814       sprintf(buf, "name %s\n", gameInfo.white);
10815       SendToProgram(buf, &first);
10816     }
10817     if (first.sendTime) {
10818       if (first.useColors) {
10819         SendToProgram("white\n", &first); /*gnu kludge*/
10820       }
10821       SendTimeRemaining(&first, FALSE);
10822     }
10823     if (first.useColors) {
10824       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10825     }
10826     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10827     SetMachineThinkingEnables();
10828     first.maybeThinking = TRUE;
10829     StartClocks();
10830
10831     if (appData.autoFlipView && flipView) {
10832       flipView = !flipView;
10833       DrawPosition(FALSE, NULL);
10834       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10835     }
10836     if(bookHit) { // [HGM] book: simulate book reply
10837         static char bookMove[MSG_SIZ]; // a bit generous?
10838
10839         programStats.nodes = programStats.depth = programStats.time = 
10840         programStats.score = programStats.got_only_move = 0;
10841         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10842
10843         strcpy(bookMove, "move ");
10844         strcat(bookMove, bookHit);
10845         HandleMachineMove(bookMove, &first);
10846     }
10847 }
10848
10849
10850 void
10851 DisplayTwoMachinesTitle()
10852 {
10853     char buf[MSG_SIZ];
10854     if (appData.matchGames > 0) {
10855         if (first.twoMachinesColor[0] == 'w') {
10856             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10857                     gameInfo.white, gameInfo.black,
10858                     first.matchWins, second.matchWins,
10859                     matchGame - 1 - (first.matchWins + second.matchWins));
10860         } else {
10861             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10862                     gameInfo.white, gameInfo.black,
10863                     second.matchWins, first.matchWins,
10864                     matchGame - 1 - (first.matchWins + second.matchWins));
10865         }
10866     } else {
10867         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10868     }
10869     DisplayTitle(buf);
10870 }
10871
10872 void
10873 TwoMachinesEvent P((void))
10874 {
10875     int i;
10876     char buf[MSG_SIZ];
10877     ChessProgramState *onmove;
10878     char *bookHit = NULL;
10879     
10880     if (appData.noChessProgram) return;
10881
10882     switch (gameMode) {
10883       case TwoMachinesPlay:
10884         return;
10885       case MachinePlaysWhite:
10886       case MachinePlaysBlack:
10887         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10888             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10889             return;
10890         }
10891         /* fall through */
10892       case BeginningOfGame:
10893       case PlayFromGameFile:
10894       case EndOfGame:
10895         EditGameEvent();
10896         if (gameMode != EditGame) return;
10897         break;
10898       case EditPosition:
10899         EditPositionDone();
10900         break;
10901       case AnalyzeMode:
10902       case AnalyzeFile:
10903         ExitAnalyzeMode();
10904         break;
10905       case EditGame:
10906       default:
10907         break;
10908     }
10909
10910     forwardMostMove = currentMove;
10911     ResurrectChessProgram();    /* in case first program isn't running */
10912
10913     if (second.pr == NULL) {
10914         StartChessProgram(&second);
10915         if (second.protocolVersion == 1) {
10916           TwoMachinesEventIfReady();
10917         } else {
10918           /* kludge: allow timeout for initial "feature" command */
10919           FreezeUI();
10920           DisplayMessage("", _("Starting second chess program"));
10921           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10922         }
10923         return;
10924     }
10925     DisplayMessage("", "");
10926     InitChessProgram(&second, FALSE);
10927     SendToProgram("force\n", &second);
10928     if (startedFromSetupPosition) {
10929         SendBoard(&second, backwardMostMove);
10930     if (appData.debugMode) {
10931         fprintf(debugFP, "Two Machines\n");
10932     }
10933     }
10934     for (i = backwardMostMove; i < forwardMostMove; i++) {
10935         SendMoveToProgram(i, &second);
10936     }
10937
10938     gameMode = TwoMachinesPlay;
10939     pausing = FALSE;
10940     ModeHighlight();
10941     SetGameInfo();
10942     DisplayTwoMachinesTitle();
10943     firstMove = TRUE;
10944     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10945         onmove = &first;
10946     } else {
10947         onmove = &second;
10948     }
10949
10950     SendToProgram(first.computerString, &first);
10951     if (first.sendName) {
10952       sprintf(buf, "name %s\n", second.tidy);
10953       SendToProgram(buf, &first);
10954     }
10955     SendToProgram(second.computerString, &second);
10956     if (second.sendName) {
10957       sprintf(buf, "name %s\n", first.tidy);
10958       SendToProgram(buf, &second);
10959     }
10960
10961     ResetClocks();
10962     if (!first.sendTime || !second.sendTime) {
10963         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10964         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10965     }
10966     if (onmove->sendTime) {
10967       if (onmove->useColors) {
10968         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10969       }
10970       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10971     }
10972     if (onmove->useColors) {
10973       SendToProgram(onmove->twoMachinesColor, onmove);
10974     }
10975     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10976 //    SendToProgram("go\n", onmove);
10977     onmove->maybeThinking = TRUE;
10978     SetMachineThinkingEnables();
10979
10980     StartClocks();
10981
10982     if(bookHit) { // [HGM] book: simulate book reply
10983         static char bookMove[MSG_SIZ]; // a bit generous?
10984
10985         programStats.nodes = programStats.depth = programStats.time = 
10986         programStats.score = programStats.got_only_move = 0;
10987         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10988
10989         strcpy(bookMove, "move ");
10990         strcat(bookMove, bookHit);
10991         savedMessage = bookMove; // args for deferred call
10992         savedState = onmove;
10993         ScheduleDelayedEvent(DeferredBookMove, 1);
10994     }
10995 }
10996
10997 void
10998 TrainingEvent()
10999 {
11000     if (gameMode == Training) {
11001       SetTrainingModeOff();
11002       gameMode = PlayFromGameFile;
11003       DisplayMessage("", _("Training mode off"));
11004     } else {
11005       gameMode = Training;
11006       animateTraining = appData.animate;
11007
11008       /* make sure we are not already at the end of the game */
11009       if (currentMove < forwardMostMove) {
11010         SetTrainingModeOn();
11011         DisplayMessage("", _("Training mode on"));
11012       } else {
11013         gameMode = PlayFromGameFile;
11014         DisplayError(_("Already at end of game"), 0);
11015       }
11016     }
11017     ModeHighlight();
11018 }
11019
11020 void
11021 IcsClientEvent()
11022 {
11023     if (!appData.icsActive) return;
11024     switch (gameMode) {
11025       case IcsPlayingWhite:
11026       case IcsPlayingBlack:
11027       case IcsObserving:
11028       case IcsIdle:
11029       case BeginningOfGame:
11030       case IcsExamining:
11031         return;
11032
11033       case EditGame:
11034         break;
11035
11036       case EditPosition:
11037         EditPositionDone();
11038         break;
11039
11040       case AnalyzeMode:
11041       case AnalyzeFile:
11042         ExitAnalyzeMode();
11043         break;
11044         
11045       default:
11046         EditGameEvent();
11047         break;
11048     }
11049
11050     gameMode = IcsIdle;
11051     ModeHighlight();
11052     return;
11053 }
11054
11055
11056 void
11057 EditGameEvent()
11058 {
11059     int i;
11060
11061     switch (gameMode) {
11062       case Training:
11063         SetTrainingModeOff();
11064         break;
11065       case MachinePlaysWhite:
11066       case MachinePlaysBlack:
11067       case BeginningOfGame:
11068         SendToProgram("force\n", &first);
11069         SetUserThinkingEnables();
11070         break;
11071       case PlayFromGameFile:
11072         (void) StopLoadGameTimer();
11073         if (gameFileFP != NULL) {
11074             gameFileFP = NULL;
11075         }
11076         break;
11077       case EditPosition:
11078         EditPositionDone();
11079         break;
11080       case AnalyzeMode:
11081       case AnalyzeFile:
11082         ExitAnalyzeMode();
11083         SendToProgram("force\n", &first);
11084         break;
11085       case TwoMachinesPlay:
11086         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11087         ResurrectChessProgram();
11088         SetUserThinkingEnables();
11089         break;
11090       case EndOfGame:
11091         ResurrectChessProgram();
11092         break;
11093       case IcsPlayingBlack:
11094       case IcsPlayingWhite:
11095         DisplayError(_("Warning: You are still playing a game"), 0);
11096         break;
11097       case IcsObserving:
11098         DisplayError(_("Warning: You are still observing a game"), 0);
11099         break;
11100       case IcsExamining:
11101         DisplayError(_("Warning: You are still examining a game"), 0);
11102         break;
11103       case IcsIdle:
11104         break;
11105       case EditGame:
11106       default:
11107         return;
11108     }
11109     
11110     pausing = FALSE;
11111     StopClocks();
11112     first.offeredDraw = second.offeredDraw = 0;
11113
11114     if (gameMode == PlayFromGameFile) {
11115         whiteTimeRemaining = timeRemaining[0][currentMove];
11116         blackTimeRemaining = timeRemaining[1][currentMove];
11117         DisplayTitle("");
11118     }
11119
11120     if (gameMode == MachinePlaysWhite ||
11121         gameMode == MachinePlaysBlack ||
11122         gameMode == TwoMachinesPlay ||
11123         gameMode == EndOfGame) {
11124         i = forwardMostMove;
11125         while (i > currentMove) {
11126             SendToProgram("undo\n", &first);
11127             i--;
11128         }
11129         whiteTimeRemaining = timeRemaining[0][currentMove];
11130         blackTimeRemaining = timeRemaining[1][currentMove];
11131         DisplayBothClocks();
11132         if (whiteFlag || blackFlag) {
11133             whiteFlag = blackFlag = 0;
11134         }
11135         DisplayTitle("");
11136     }           
11137     
11138     gameMode = EditGame;
11139     ModeHighlight();
11140     SetGameInfo();
11141 }
11142
11143
11144 void
11145 EditPositionEvent()
11146 {
11147     if (gameMode == EditPosition) {
11148         EditGameEvent();
11149         return;
11150     }
11151     
11152     EditGameEvent();
11153     if (gameMode != EditGame) return;
11154     
11155     gameMode = EditPosition;
11156     ModeHighlight();
11157     SetGameInfo();
11158     if (currentMove > 0)
11159       CopyBoard(boards[0], boards[currentMove]);
11160     
11161     blackPlaysFirst = !WhiteOnMove(currentMove);
11162     ResetClocks();
11163     currentMove = forwardMostMove = backwardMostMove = 0;
11164     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11165     DisplayMove(-1);
11166 }
11167
11168 void
11169 ExitAnalyzeMode()
11170 {
11171     /* [DM] icsEngineAnalyze - possible call from other functions */
11172     if (appData.icsEngineAnalyze) {
11173         appData.icsEngineAnalyze = FALSE;
11174
11175         DisplayMessage("",_("Close ICS engine analyze..."));
11176     }
11177     if (first.analysisSupport && first.analyzing) {
11178       SendToProgram("exit\n", &first);
11179       first.analyzing = FALSE;
11180     }
11181     thinkOutput[0] = NULLCHAR;
11182 }
11183
11184 void
11185 EditPositionDone()
11186 {
11187     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11188
11189     startedFromSetupPosition = TRUE;
11190     InitChessProgram(&first, FALSE);
11191     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11192     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11193         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11194         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11195     } else castlingRights[0][2] = -1;
11196     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11197         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11198         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11199     } else castlingRights[0][5] = -1;
11200     SendToProgram("force\n", &first);
11201     if (blackPlaysFirst) {
11202         strcpy(moveList[0], "");
11203         strcpy(parseList[0], "");
11204         currentMove = forwardMostMove = backwardMostMove = 1;
11205         CopyBoard(boards[1], boards[0]);
11206         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11207         { int i;
11208           epStatus[1] = epStatus[0];
11209           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11210         }
11211     } else {
11212         currentMove = forwardMostMove = backwardMostMove = 0;
11213     }
11214     SendBoard(&first, forwardMostMove);
11215     if (appData.debugMode) {
11216         fprintf(debugFP, "EditPosDone\n");
11217     }
11218     DisplayTitle("");
11219     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11220     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11221     gameMode = EditGame;
11222     ModeHighlight();
11223     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11224     ClearHighlights(); /* [AS] */
11225 }
11226
11227 /* Pause for `ms' milliseconds */
11228 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11229 void
11230 TimeDelay(ms)
11231      long ms;
11232 {
11233     TimeMark m1, m2;
11234
11235     GetTimeMark(&m1);
11236     do {
11237         GetTimeMark(&m2);
11238     } while (SubtractTimeMarks(&m2, &m1) < ms);
11239 }
11240
11241 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11242 void
11243 SendMultiLineToICS(buf)
11244      char *buf;
11245 {
11246     char temp[MSG_SIZ+1], *p;
11247     int len;
11248
11249     len = strlen(buf);
11250     if (len > MSG_SIZ)
11251       len = MSG_SIZ;
11252   
11253     strncpy(temp, buf, len);
11254     temp[len] = 0;
11255
11256     p = temp;
11257     while (*p) {
11258         if (*p == '\n' || *p == '\r')
11259           *p = ' ';
11260         ++p;
11261     }
11262
11263     strcat(temp, "\n");
11264     SendToICS(temp);
11265     SendToPlayer(temp, strlen(temp));
11266 }
11267
11268 void
11269 SetWhiteToPlayEvent()
11270 {
11271     if (gameMode == EditPosition) {
11272         blackPlaysFirst = FALSE;
11273         DisplayBothClocks();    /* works because currentMove is 0 */
11274     } else if (gameMode == IcsExamining) {
11275         SendToICS(ics_prefix);
11276         SendToICS("tomove white\n");
11277     }
11278 }
11279
11280 void
11281 SetBlackToPlayEvent()
11282 {
11283     if (gameMode == EditPosition) {
11284         blackPlaysFirst = TRUE;
11285         currentMove = 1;        /* kludge */
11286         DisplayBothClocks();
11287         currentMove = 0;
11288     } else if (gameMode == IcsExamining) {
11289         SendToICS(ics_prefix);
11290         SendToICS("tomove black\n");
11291     }
11292 }
11293
11294 void
11295 EditPositionMenuEvent(selection, x, y)
11296      ChessSquare selection;
11297      int x, y;
11298 {
11299     char buf[MSG_SIZ];
11300     ChessSquare piece = boards[0][y][x];
11301
11302     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11303
11304     switch (selection) {
11305       case ClearBoard:
11306         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11307             SendToICS(ics_prefix);
11308             SendToICS("bsetup clear\n");
11309         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11310             SendToICS(ics_prefix);
11311             SendToICS("clearboard\n");
11312         } else {
11313             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11314                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11315                 for (y = 0; y < BOARD_HEIGHT; y++) {
11316                     if (gameMode == IcsExamining) {
11317                         if (boards[currentMove][y][x] != EmptySquare) {
11318                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11319                                     AAA + x, ONE + y);
11320                             SendToICS(buf);
11321                         }
11322                     } else {
11323                         boards[0][y][x] = p;
11324                     }
11325                 }
11326             }
11327         }
11328         if (gameMode == EditPosition) {
11329             DrawPosition(FALSE, boards[0]);
11330         }
11331         break;
11332
11333       case WhitePlay:
11334         SetWhiteToPlayEvent();
11335         break;
11336
11337       case BlackPlay:
11338         SetBlackToPlayEvent();
11339         break;
11340
11341       case EmptySquare:
11342         if (gameMode == IcsExamining) {
11343             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11344             SendToICS(buf);
11345         } else {
11346             boards[0][y][x] = EmptySquare;
11347             DrawPosition(FALSE, boards[0]);
11348         }
11349         break;
11350
11351       case PromotePiece:
11352         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11353            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11354             selection = (ChessSquare) (PROMOTED piece);
11355         } else if(piece == EmptySquare) selection = WhiteSilver;
11356         else selection = (ChessSquare)((int)piece - 1);
11357         goto defaultlabel;
11358
11359       case DemotePiece:
11360         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11361            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11362             selection = (ChessSquare) (DEMOTED piece);
11363         } else if(piece == EmptySquare) selection = BlackSilver;
11364         else selection = (ChessSquare)((int)piece + 1);       
11365         goto defaultlabel;
11366
11367       case WhiteQueen:
11368       case BlackQueen:
11369         if(gameInfo.variant == VariantShatranj ||
11370            gameInfo.variant == VariantXiangqi  ||
11371            gameInfo.variant == VariantCourier    )
11372             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11373         goto defaultlabel;
11374
11375       case WhiteKing:
11376       case BlackKing:
11377         if(gameInfo.variant == VariantXiangqi)
11378             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11379         if(gameInfo.variant == VariantKnightmate)
11380             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11381       default:
11382         defaultlabel:
11383         if (gameMode == IcsExamining) {
11384             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11385                     PieceToChar(selection), AAA + x, ONE + y);
11386             SendToICS(buf);
11387         } else {
11388             boards[0][y][x] = selection;
11389             DrawPosition(FALSE, boards[0]);
11390         }
11391         break;
11392     }
11393 }
11394
11395
11396 void
11397 DropMenuEvent(selection, x, y)
11398      ChessSquare selection;
11399      int x, y;
11400 {
11401     ChessMove moveType;
11402
11403     switch (gameMode) {
11404       case IcsPlayingWhite:
11405       case MachinePlaysBlack:
11406         if (!WhiteOnMove(currentMove)) {
11407             DisplayMoveError(_("It is Black's turn"));
11408             return;
11409         }
11410         moveType = WhiteDrop;
11411         break;
11412       case IcsPlayingBlack:
11413       case MachinePlaysWhite:
11414         if (WhiteOnMove(currentMove)) {
11415             DisplayMoveError(_("It is White's turn"));
11416             return;
11417         }
11418         moveType = BlackDrop;
11419         break;
11420       case EditGame:
11421         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11422         break;
11423       default:
11424         return;
11425     }
11426
11427     if (moveType == BlackDrop && selection < BlackPawn) {
11428       selection = (ChessSquare) ((int) selection
11429                                  + (int) BlackPawn - (int) WhitePawn);
11430     }
11431     if (boards[currentMove][y][x] != EmptySquare) {
11432         DisplayMoveError(_("That square is occupied"));
11433         return;
11434     }
11435
11436     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11437 }
11438
11439 void
11440 AcceptEvent()
11441 {
11442     /* Accept a pending offer of any kind from opponent */
11443     
11444     if (appData.icsActive) {
11445         SendToICS(ics_prefix);
11446         SendToICS("accept\n");
11447     } else if (cmailMsgLoaded) {
11448         if (currentMove == cmailOldMove &&
11449             commentList[cmailOldMove] != NULL &&
11450             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11451                    "Black offers a draw" : "White offers a draw")) {
11452             TruncateGame();
11453             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11454             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11455         } else {
11456             DisplayError(_("There is no pending offer on this move"), 0);
11457             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11458         }
11459     } else {
11460         /* Not used for offers from chess program */
11461     }
11462 }
11463
11464 void
11465 DeclineEvent()
11466 {
11467     /* Decline a pending offer of any kind from opponent */
11468     
11469     if (appData.icsActive) {
11470         SendToICS(ics_prefix);
11471         SendToICS("decline\n");
11472     } else if (cmailMsgLoaded) {
11473         if (currentMove == cmailOldMove &&
11474             commentList[cmailOldMove] != NULL &&
11475             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11476                    "Black offers a draw" : "White offers a draw")) {
11477 #ifdef NOTDEF
11478             AppendComment(cmailOldMove, "Draw declined");
11479             DisplayComment(cmailOldMove - 1, "Draw declined");
11480 #endif /*NOTDEF*/
11481         } else {
11482             DisplayError(_("There is no pending offer on this move"), 0);
11483         }
11484     } else {
11485         /* Not used for offers from chess program */
11486     }
11487 }
11488
11489 void
11490 RematchEvent()
11491 {
11492     /* Issue ICS rematch command */
11493     if (appData.icsActive) {
11494         SendToICS(ics_prefix);
11495         SendToICS("rematch\n");
11496     }
11497 }
11498
11499 void
11500 CallFlagEvent()
11501 {
11502     /* Call your opponent's flag (claim a win on time) */
11503     if (appData.icsActive) {
11504         SendToICS(ics_prefix);
11505         SendToICS("flag\n");
11506     } else {
11507         switch (gameMode) {
11508           default:
11509             return;
11510           case MachinePlaysWhite:
11511             if (whiteFlag) {
11512                 if (blackFlag)
11513                   GameEnds(GameIsDrawn, "Both players ran out of time",
11514                            GE_PLAYER);
11515                 else
11516                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11517             } else {
11518                 DisplayError(_("Your opponent is not out of time"), 0);
11519             }
11520             break;
11521           case MachinePlaysBlack:
11522             if (blackFlag) {
11523                 if (whiteFlag)
11524                   GameEnds(GameIsDrawn, "Both players ran out of time",
11525                            GE_PLAYER);
11526                 else
11527                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11528             } else {
11529                 DisplayError(_("Your opponent is not out of time"), 0);
11530             }
11531             break;
11532         }
11533     }
11534 }
11535
11536 void
11537 DrawEvent()
11538 {
11539     /* Offer draw or accept pending draw offer from opponent */
11540     
11541     if (appData.icsActive) {
11542         /* Note: tournament rules require draw offers to be
11543            made after you make your move but before you punch
11544            your clock.  Currently ICS doesn't let you do that;
11545            instead, you immediately punch your clock after making
11546            a move, but you can offer a draw at any time. */
11547         
11548         SendToICS(ics_prefix);
11549         SendToICS("draw\n");
11550     } else if (cmailMsgLoaded) {
11551         if (currentMove == cmailOldMove &&
11552             commentList[cmailOldMove] != NULL &&
11553             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11554                    "Black offers a draw" : "White offers a draw")) {
11555             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11556             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11557         } else if (currentMove == cmailOldMove + 1) {
11558             char *offer = WhiteOnMove(cmailOldMove) ?
11559               "White offers a draw" : "Black offers a draw";
11560             AppendComment(currentMove, offer);
11561             DisplayComment(currentMove - 1, offer);
11562             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11563         } else {
11564             DisplayError(_("You must make your move before offering a draw"), 0);
11565             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11566         }
11567     } else if (first.offeredDraw) {
11568         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11569     } else {
11570         if (first.sendDrawOffers) {
11571             SendToProgram("draw\n", &first);
11572             userOfferedDraw = TRUE;
11573         }
11574     }
11575 }
11576
11577 void
11578 AdjournEvent()
11579 {
11580     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11581     
11582     if (appData.icsActive) {
11583         SendToICS(ics_prefix);
11584         SendToICS("adjourn\n");
11585     } else {
11586         /* Currently GNU Chess doesn't offer or accept Adjourns */
11587     }
11588 }
11589
11590
11591 void
11592 AbortEvent()
11593 {
11594     /* Offer Abort or accept pending Abort offer from opponent */
11595     
11596     if (appData.icsActive) {
11597         SendToICS(ics_prefix);
11598         SendToICS("abort\n");
11599     } else {
11600         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11601     }
11602 }
11603
11604 void
11605 ResignEvent()
11606 {
11607     /* Resign.  You can do this even if it's not your turn. */
11608     
11609     if (appData.icsActive) {
11610         SendToICS(ics_prefix);
11611         SendToICS("resign\n");
11612     } else {
11613         switch (gameMode) {
11614           case MachinePlaysWhite:
11615             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11616             break;
11617           case MachinePlaysBlack:
11618             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11619             break;
11620           case EditGame:
11621             if (cmailMsgLoaded) {
11622                 TruncateGame();
11623                 if (WhiteOnMove(cmailOldMove)) {
11624                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11625                 } else {
11626                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11627                 }
11628                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11629             }
11630             break;
11631           default:
11632             break;
11633         }
11634     }
11635 }
11636
11637
11638 void
11639 StopObservingEvent()
11640 {
11641     /* Stop observing current games */
11642     SendToICS(ics_prefix);
11643     SendToICS("unobserve\n");
11644 }
11645
11646 void
11647 StopExaminingEvent()
11648 {
11649     /* Stop observing current game */
11650     SendToICS(ics_prefix);
11651     SendToICS("unexamine\n");
11652 }
11653
11654 void
11655 ForwardInner(target)
11656      int target;
11657 {
11658     int limit;
11659
11660     if (appData.debugMode)
11661         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11662                 target, currentMove, forwardMostMove);
11663
11664     if (gameMode == EditPosition)
11665       return;
11666
11667     if (gameMode == PlayFromGameFile && !pausing)
11668       PauseEvent();
11669     
11670     if (gameMode == IcsExamining && pausing)
11671       limit = pauseExamForwardMostMove;
11672     else
11673       limit = forwardMostMove;
11674     
11675     if (target > limit) target = limit;
11676
11677     if (target > 0 && moveList[target - 1][0]) {
11678         int fromX, fromY, toX, toY;
11679         toX = moveList[target - 1][2] - AAA;
11680         toY = moveList[target - 1][3] - ONE;
11681         if (moveList[target - 1][1] == '@') {
11682             if (appData.highlightLastMove) {
11683                 SetHighlights(-1, -1, toX, toY);
11684             }
11685         } else {
11686             fromX = moveList[target - 1][0] - AAA;
11687             fromY = moveList[target - 1][1] - ONE;
11688             if (target == currentMove + 1) {
11689                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11690             }
11691             if (appData.highlightLastMove) {
11692                 SetHighlights(fromX, fromY, toX, toY);
11693             }
11694         }
11695     }
11696     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11697         gameMode == Training || gameMode == PlayFromGameFile || 
11698         gameMode == AnalyzeFile) {
11699         while (currentMove < target) {
11700             SendMoveToProgram(currentMove++, &first);
11701         }
11702     } else {
11703         currentMove = target;
11704     }
11705     
11706     if (gameMode == EditGame || gameMode == EndOfGame) {
11707         whiteTimeRemaining = timeRemaining[0][currentMove];
11708         blackTimeRemaining = timeRemaining[1][currentMove];
11709     }
11710     DisplayBothClocks();
11711     DisplayMove(currentMove - 1);
11712     DrawPosition(FALSE, boards[currentMove]);
11713     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11714     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11715         DisplayComment(currentMove - 1, commentList[currentMove]);
11716     }
11717 }
11718
11719
11720 void
11721 ForwardEvent()
11722 {
11723     if (gameMode == IcsExamining && !pausing) {
11724         SendToICS(ics_prefix);
11725         SendToICS("forward\n");
11726     } else {
11727         ForwardInner(currentMove + 1);
11728     }
11729 }
11730
11731 void
11732 ToEndEvent()
11733 {
11734     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11735         /* to optimze, we temporarily turn off analysis mode while we feed
11736          * the remaining moves to the engine. Otherwise we get analysis output
11737          * after each move.
11738          */ 
11739         if (first.analysisSupport) {
11740           SendToProgram("exit\nforce\n", &first);
11741           first.analyzing = FALSE;
11742         }
11743     }
11744         
11745     if (gameMode == IcsExamining && !pausing) {
11746         SendToICS(ics_prefix);
11747         SendToICS("forward 999999\n");
11748     } else {
11749         ForwardInner(forwardMostMove);
11750     }
11751
11752     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11753         /* we have fed all the moves, so reactivate analysis mode */
11754         SendToProgram("analyze\n", &first);
11755         first.analyzing = TRUE;
11756         /*first.maybeThinking = TRUE;*/
11757         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11758     }
11759 }
11760
11761 void
11762 BackwardInner(target)
11763      int target;
11764 {
11765     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11766
11767     if (appData.debugMode)
11768         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11769                 target, currentMove, forwardMostMove);
11770
11771     if (gameMode == EditPosition) return;
11772     if (currentMove <= backwardMostMove) {
11773         ClearHighlights();
11774         DrawPosition(full_redraw, boards[currentMove]);
11775         return;
11776     }
11777     if (gameMode == PlayFromGameFile && !pausing)
11778       PauseEvent();
11779     
11780     if (moveList[target][0]) {
11781         int fromX, fromY, toX, toY;
11782         toX = moveList[target][2] - AAA;
11783         toY = moveList[target][3] - ONE;
11784         if (moveList[target][1] == '@') {
11785             if (appData.highlightLastMove) {
11786                 SetHighlights(-1, -1, toX, toY);
11787             }
11788         } else {
11789             fromX = moveList[target][0] - AAA;
11790             fromY = moveList[target][1] - ONE;
11791             if (target == currentMove - 1) {
11792                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11793             }
11794             if (appData.highlightLastMove) {
11795                 SetHighlights(fromX, fromY, toX, toY);
11796             }
11797         }
11798     }
11799     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11800         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11801         while (currentMove > target) {
11802             SendToProgram("undo\n", &first);
11803             currentMove--;
11804         }
11805     } else {
11806         currentMove = target;
11807     }
11808     
11809     if (gameMode == EditGame || gameMode == EndOfGame) {
11810         whiteTimeRemaining = timeRemaining[0][currentMove];
11811         blackTimeRemaining = timeRemaining[1][currentMove];
11812     }
11813     DisplayBothClocks();
11814     DisplayMove(currentMove - 1);
11815     DrawPosition(full_redraw, boards[currentMove]);
11816     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11817     // [HGM] PV info: routine tests if comment empty
11818     DisplayComment(currentMove - 1, commentList[currentMove]);
11819 }
11820
11821 void
11822 BackwardEvent()
11823 {
11824     if (gameMode == IcsExamining && !pausing) {
11825         SendToICS(ics_prefix);
11826         SendToICS("backward\n");
11827     } else {
11828         BackwardInner(currentMove - 1);
11829     }
11830 }
11831
11832 void
11833 ToStartEvent()
11834 {
11835     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11836         /* to optimze, we temporarily turn off analysis mode while we undo
11837          * all the moves. Otherwise we get analysis output after each undo.
11838          */ 
11839         if (first.analysisSupport) {
11840           SendToProgram("exit\nforce\n", &first);
11841           first.analyzing = FALSE;
11842         }
11843     }
11844
11845     if (gameMode == IcsExamining && !pausing) {
11846         SendToICS(ics_prefix);
11847         SendToICS("backward 999999\n");
11848     } else {
11849         BackwardInner(backwardMostMove);
11850     }
11851
11852     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11853         /* we have fed all the moves, so reactivate analysis mode */
11854         SendToProgram("analyze\n", &first);
11855         first.analyzing = TRUE;
11856         /*first.maybeThinking = TRUE;*/
11857         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11858     }
11859 }
11860
11861 void
11862 ToNrEvent(int to)
11863 {
11864   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11865   if (to >= forwardMostMove) to = forwardMostMove;
11866   if (to <= backwardMostMove) to = backwardMostMove;
11867   if (to < currentMove) {
11868     BackwardInner(to);
11869   } else {
11870     ForwardInner(to);
11871   }
11872 }
11873
11874 void
11875 RevertEvent()
11876 {
11877     if (gameMode != IcsExamining) {
11878         DisplayError(_("You are not examining a game"), 0);
11879         return;
11880     }
11881     if (pausing) {
11882         DisplayError(_("You can't revert while pausing"), 0);
11883         return;
11884     }
11885     SendToICS(ics_prefix);
11886     SendToICS("revert\n");
11887 }
11888
11889 void
11890 RetractMoveEvent()
11891 {
11892     switch (gameMode) {
11893       case MachinePlaysWhite:
11894       case MachinePlaysBlack:
11895         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11896             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11897             return;
11898         }
11899         if (forwardMostMove < 2) return;
11900         currentMove = forwardMostMove = forwardMostMove - 2;
11901         whiteTimeRemaining = timeRemaining[0][currentMove];
11902         blackTimeRemaining = timeRemaining[1][currentMove];
11903         DisplayBothClocks();
11904         DisplayMove(currentMove - 1);
11905         ClearHighlights();/*!! could figure this out*/
11906         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11907         SendToProgram("remove\n", &first);
11908         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11909         break;
11910
11911       case BeginningOfGame:
11912       default:
11913         break;
11914
11915       case IcsPlayingWhite:
11916       case IcsPlayingBlack:
11917         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11918             SendToICS(ics_prefix);
11919             SendToICS("takeback 2\n");
11920         } else {
11921             SendToICS(ics_prefix);
11922             SendToICS("takeback 1\n");
11923         }
11924         break;
11925     }
11926 }
11927
11928 void
11929 MoveNowEvent()
11930 {
11931     ChessProgramState *cps;
11932
11933     switch (gameMode) {
11934       case MachinePlaysWhite:
11935         if (!WhiteOnMove(forwardMostMove)) {
11936             DisplayError(_("It is your turn"), 0);
11937             return;
11938         }
11939         cps = &first;
11940         break;
11941       case MachinePlaysBlack:
11942         if (WhiteOnMove(forwardMostMove)) {
11943             DisplayError(_("It is your turn"), 0);
11944             return;
11945         }
11946         cps = &first;
11947         break;
11948       case TwoMachinesPlay:
11949         if (WhiteOnMove(forwardMostMove) ==
11950             (first.twoMachinesColor[0] == 'w')) {
11951             cps = &first;
11952         } else {
11953             cps = &second;
11954         }
11955         break;
11956       case BeginningOfGame:
11957       default:
11958         return;
11959     }
11960     SendToProgram("?\n", cps);
11961 }
11962
11963 void
11964 TruncateGameEvent()
11965 {
11966     EditGameEvent();
11967     if (gameMode != EditGame) return;
11968     TruncateGame();
11969 }
11970
11971 void
11972 TruncateGame()
11973 {
11974     if (forwardMostMove > currentMove) {
11975         if (gameInfo.resultDetails != NULL) {
11976             free(gameInfo.resultDetails);
11977             gameInfo.resultDetails = NULL;
11978             gameInfo.result = GameUnfinished;
11979         }
11980         forwardMostMove = currentMove;
11981         HistorySet(parseList, backwardMostMove, forwardMostMove,
11982                    currentMove-1);
11983     }
11984 }
11985
11986 void
11987 HintEvent()
11988 {
11989     if (appData.noChessProgram) return;
11990     switch (gameMode) {
11991       case MachinePlaysWhite:
11992         if (WhiteOnMove(forwardMostMove)) {
11993             DisplayError(_("Wait until your turn"), 0);
11994             return;
11995         }
11996         break;
11997       case BeginningOfGame:
11998       case MachinePlaysBlack:
11999         if (!WhiteOnMove(forwardMostMove)) {
12000             DisplayError(_("Wait until your turn"), 0);
12001             return;
12002         }
12003         break;
12004       default:
12005         DisplayError(_("No hint available"), 0);
12006         return;
12007     }
12008     SendToProgram("hint\n", &first);
12009     hintRequested = TRUE;
12010 }
12011
12012 void
12013 BookEvent()
12014 {
12015     if (appData.noChessProgram) return;
12016     switch (gameMode) {
12017       case MachinePlaysWhite:
12018         if (WhiteOnMove(forwardMostMove)) {
12019             DisplayError(_("Wait until your turn"), 0);
12020             return;
12021         }
12022         break;
12023       case BeginningOfGame:
12024       case MachinePlaysBlack:
12025         if (!WhiteOnMove(forwardMostMove)) {
12026             DisplayError(_("Wait until your turn"), 0);
12027             return;
12028         }
12029         break;
12030       case EditPosition:
12031         EditPositionDone();
12032         break;
12033       case TwoMachinesPlay:
12034         return;
12035       default:
12036         break;
12037     }
12038     SendToProgram("bk\n", &first);
12039     bookOutput[0] = NULLCHAR;
12040     bookRequested = TRUE;
12041 }
12042
12043 void
12044 AboutGameEvent()
12045 {
12046     char *tags = PGNTags(&gameInfo);
12047     TagsPopUp(tags, CmailMsg());
12048     free(tags);
12049 }
12050
12051 /* end button procedures */
12052
12053 void
12054 PrintPosition(fp, move)
12055      FILE *fp;
12056      int move;
12057 {
12058     int i, j;
12059     
12060     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12061         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12062             char c = PieceToChar(boards[move][i][j]);
12063             fputc(c == 'x' ? '.' : c, fp);
12064             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12065         }
12066     }
12067     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12068       fprintf(fp, "white to play\n");
12069     else
12070       fprintf(fp, "black to play\n");
12071 }
12072
12073 void
12074 PrintOpponents(fp)
12075      FILE *fp;
12076 {
12077     if (gameInfo.white != NULL) {
12078         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12079     } else {
12080         fprintf(fp, "\n");
12081     }
12082 }
12083
12084 /* Find last component of program's own name, using some heuristics */
12085 void
12086 TidyProgramName(prog, host, buf)
12087      char *prog, *host, buf[MSG_SIZ];
12088 {
12089     char *p, *q;
12090     int local = (strcmp(host, "localhost") == 0);
12091     while (!local && (p = strchr(prog, ';')) != NULL) {
12092         p++;
12093         while (*p == ' ') p++;
12094         prog = p;
12095     }
12096     if (*prog == '"' || *prog == '\'') {
12097         q = strchr(prog + 1, *prog);
12098     } else {
12099         q = strchr(prog, ' ');
12100     }
12101     if (q == NULL) q = prog + strlen(prog);
12102     p = q;
12103     while (p >= prog && *p != '/' && *p != '\\') p--;
12104     p++;
12105     if(p == prog && *p == '"') p++;
12106     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12107     memcpy(buf, p, q - p);
12108     buf[q - p] = NULLCHAR;
12109     if (!local) {
12110         strcat(buf, "@");
12111         strcat(buf, host);
12112     }
12113 }
12114
12115 char *
12116 TimeControlTagValue()
12117 {
12118     char buf[MSG_SIZ];
12119     if (!appData.clockMode) {
12120         strcpy(buf, "-");
12121     } else if (movesPerSession > 0) {
12122         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12123     } else if (timeIncrement == 0) {
12124         sprintf(buf, "%ld", timeControl/1000);
12125     } else {
12126         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12127     }
12128     return StrSave(buf);
12129 }
12130
12131 void
12132 SetGameInfo()
12133 {
12134     /* This routine is used only for certain modes */
12135     VariantClass v = gameInfo.variant;
12136     ClearGameInfo(&gameInfo);
12137     gameInfo.variant = v;
12138
12139     switch (gameMode) {
12140       case MachinePlaysWhite:
12141         gameInfo.event = StrSave( appData.pgnEventHeader );
12142         gameInfo.site = StrSave(HostName());
12143         gameInfo.date = PGNDate();
12144         gameInfo.round = StrSave("-");
12145         gameInfo.white = StrSave(first.tidy);
12146         gameInfo.black = StrSave(UserName());
12147         gameInfo.timeControl = TimeControlTagValue();
12148         break;
12149
12150       case MachinePlaysBlack:
12151         gameInfo.event = StrSave( appData.pgnEventHeader );
12152         gameInfo.site = StrSave(HostName());
12153         gameInfo.date = PGNDate();
12154         gameInfo.round = StrSave("-");
12155         gameInfo.white = StrSave(UserName());
12156         gameInfo.black = StrSave(first.tidy);
12157         gameInfo.timeControl = TimeControlTagValue();
12158         break;
12159
12160       case TwoMachinesPlay:
12161         gameInfo.event = StrSave( appData.pgnEventHeader );
12162         gameInfo.site = StrSave(HostName());
12163         gameInfo.date = PGNDate();
12164         if (matchGame > 0) {
12165             char buf[MSG_SIZ];
12166             sprintf(buf, "%d", matchGame);
12167             gameInfo.round = StrSave(buf);
12168         } else {
12169             gameInfo.round = StrSave("-");
12170         }
12171         if (first.twoMachinesColor[0] == 'w') {
12172             gameInfo.white = StrSave(first.tidy);
12173             gameInfo.black = StrSave(second.tidy);
12174         } else {
12175             gameInfo.white = StrSave(second.tidy);
12176             gameInfo.black = StrSave(first.tidy);
12177         }
12178         gameInfo.timeControl = TimeControlTagValue();
12179         break;
12180
12181       case EditGame:
12182         gameInfo.event = StrSave("Edited game");
12183         gameInfo.site = StrSave(HostName());
12184         gameInfo.date = PGNDate();
12185         gameInfo.round = StrSave("-");
12186         gameInfo.white = StrSave("-");
12187         gameInfo.black = StrSave("-");
12188         break;
12189
12190       case EditPosition:
12191         gameInfo.event = StrSave("Edited position");
12192         gameInfo.site = StrSave(HostName());
12193         gameInfo.date = PGNDate();
12194         gameInfo.round = StrSave("-");
12195         gameInfo.white = StrSave("-");
12196         gameInfo.black = StrSave("-");
12197         break;
12198
12199       case IcsPlayingWhite:
12200       case IcsPlayingBlack:
12201       case IcsObserving:
12202       case IcsExamining:
12203         break;
12204
12205       case PlayFromGameFile:
12206         gameInfo.event = StrSave("Game from non-PGN file");
12207         gameInfo.site = StrSave(HostName());
12208         gameInfo.date = PGNDate();
12209         gameInfo.round = StrSave("-");
12210         gameInfo.white = StrSave("?");
12211         gameInfo.black = StrSave("?");
12212         break;
12213
12214       default:
12215         break;
12216     }
12217 }
12218
12219 void
12220 ReplaceComment(index, text)
12221      int index;
12222      char *text;
12223 {
12224     int len;
12225
12226     while (*text == '\n') text++;
12227     len = strlen(text);
12228     while (len > 0 && text[len - 1] == '\n') len--;
12229
12230     if (commentList[index] != NULL)
12231       free(commentList[index]);
12232
12233     if (len == 0) {
12234         commentList[index] = NULL;
12235         return;
12236     }
12237     commentList[index] = (char *) malloc(len + 2);
12238     strncpy(commentList[index], text, len);
12239     commentList[index][len] = '\n';
12240     commentList[index][len + 1] = NULLCHAR;
12241 }
12242
12243 void
12244 CrushCRs(text)
12245      char *text;
12246 {
12247   char *p = text;
12248   char *q = text;
12249   char ch;
12250
12251   do {
12252     ch = *p++;
12253     if (ch == '\r') continue;
12254     *q++ = ch;
12255   } while (ch != '\0');
12256 }
12257
12258 void
12259 AppendComment(index, text)
12260      int index;
12261      char *text;
12262 {
12263     int oldlen, len;
12264     char *old;
12265
12266     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12267
12268     CrushCRs(text);
12269     while (*text == '\n') text++;
12270     len = strlen(text);
12271     while (len > 0 && text[len - 1] == '\n') len--;
12272
12273     if (len == 0) return;
12274
12275     if (commentList[index] != NULL) {
12276         old = commentList[index];
12277         oldlen = strlen(old);
12278         commentList[index] = (char *) malloc(oldlen + len + 2);
12279         strcpy(commentList[index], old);
12280         free(old);
12281         strncpy(&commentList[index][oldlen], text, len);
12282         commentList[index][oldlen + len] = '\n';
12283         commentList[index][oldlen + len + 1] = NULLCHAR;
12284     } else {
12285         commentList[index] = (char *) malloc(len + 2);
12286         strncpy(commentList[index], text, len);
12287         commentList[index][len] = '\n';
12288         commentList[index][len + 1] = NULLCHAR;
12289     }
12290 }
12291
12292 static char * FindStr( char * text, char * sub_text )
12293 {
12294     char * result = strstr( text, sub_text );
12295
12296     if( result != NULL ) {
12297         result += strlen( sub_text );
12298     }
12299
12300     return result;
12301 }
12302
12303 /* [AS] Try to extract PV info from PGN comment */
12304 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12305 char *GetInfoFromComment( int index, char * text )
12306 {
12307     char * sep = text;
12308
12309     if( text != NULL && index > 0 ) {
12310         int score = 0;
12311         int depth = 0;
12312         int time = -1, sec = 0, deci;
12313         char * s_eval = FindStr( text, "[%eval " );
12314         char * s_emt = FindStr( text, "[%emt " );
12315
12316         if( s_eval != NULL || s_emt != NULL ) {
12317             /* New style */
12318             char delim;
12319
12320             if( s_eval != NULL ) {
12321                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12322                     return text;
12323                 }
12324
12325                 if( delim != ']' ) {
12326                     return text;
12327                 }
12328             }
12329
12330             if( s_emt != NULL ) {
12331             }
12332         }
12333         else {
12334             /* We expect something like: [+|-]nnn.nn/dd */
12335             int score_lo = 0;
12336
12337             sep = strchr( text, '/' );
12338             if( sep == NULL || sep < (text+4) ) {
12339                 return text;
12340             }
12341
12342             time = -1; sec = -1; deci = -1;
12343             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12344                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12345                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12346                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12347                 return text;
12348             }
12349
12350             if( score_lo < 0 || score_lo >= 100 ) {
12351                 return text;
12352             }
12353
12354             if(sec >= 0) time = 600*time + 10*sec; else
12355             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12356
12357             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12358
12359             /* [HGM] PV time: now locate end of PV info */
12360             while( *++sep >= '0' && *sep <= '9'); // strip depth
12361             if(time >= 0)
12362             while( *++sep >= '0' && *sep <= '9'); // strip time
12363             if(sec >= 0)
12364             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12365             if(deci >= 0)
12366             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12367             while(*sep == ' ') sep++;
12368         }
12369
12370         if( depth <= 0 ) {
12371             return text;
12372         }
12373
12374         if( time < 0 ) {
12375             time = -1;
12376         }
12377
12378         pvInfoList[index-1].depth = depth;
12379         pvInfoList[index-1].score = score;
12380         pvInfoList[index-1].time  = 10*time; // centi-sec
12381     }
12382     return sep;
12383 }
12384
12385 void
12386 SendToProgram(message, cps)
12387      char *message;
12388      ChessProgramState *cps;
12389 {
12390     int count, outCount, error;
12391     char buf[MSG_SIZ];
12392
12393     if (cps->pr == NULL) return;
12394     Attention(cps);
12395     
12396     if (appData.debugMode) {
12397         TimeMark now;
12398         GetTimeMark(&now);
12399         fprintf(debugFP, "%ld >%-6s: %s", 
12400                 SubtractTimeMarks(&now, &programStartTime),
12401                 cps->which, message);
12402     }
12403     
12404     count = strlen(message);
12405     outCount = OutputToProcess(cps->pr, message, count, &error);
12406     if (outCount < count && !exiting 
12407                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12408         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12409         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12410             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12411                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12412                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12413             } else {
12414                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12415             }
12416             gameInfo.resultDetails = buf;
12417         }
12418         DisplayFatalError(buf, error, 1);
12419     }
12420 }
12421
12422 void
12423 ReceiveFromProgram(isr, closure, message, count, error)
12424      InputSourceRef isr;
12425      VOIDSTAR closure;
12426      char *message;
12427      int count;
12428      int error;
12429 {
12430     char *end_str;
12431     char buf[MSG_SIZ];
12432     ChessProgramState *cps = (ChessProgramState *)closure;
12433
12434     if (isr != cps->isr) return; /* Killed intentionally */
12435     if (count <= 0) {
12436         if (count == 0) {
12437             sprintf(buf,
12438                     _("Error: %s chess program (%s) exited unexpectedly"),
12439                     cps->which, cps->program);
12440         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12441                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12442                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12443                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12444                 } else {
12445                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12446                 }
12447                 gameInfo.resultDetails = buf;
12448             }
12449             RemoveInputSource(cps->isr);
12450             DisplayFatalError(buf, 0, 1);
12451         } else {
12452             sprintf(buf,
12453                     _("Error reading from %s chess program (%s)"),
12454                     cps->which, cps->program);
12455             RemoveInputSource(cps->isr);
12456
12457             /* [AS] Program is misbehaving badly... kill it */
12458             if( count == -2 ) {
12459                 DestroyChildProcess( cps->pr, 9 );
12460                 cps->pr = NoProc;
12461             }
12462
12463             DisplayFatalError(buf, error, 1);
12464         }
12465         return;
12466     }
12467     
12468     if ((end_str = strchr(message, '\r')) != NULL)
12469       *end_str = NULLCHAR;
12470     if ((end_str = strchr(message, '\n')) != NULL)
12471       *end_str = NULLCHAR;
12472     
12473     if (appData.debugMode) {
12474         TimeMark now; int print = 1;
12475         char *quote = ""; char c; int i;
12476
12477         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12478                 char start = message[0];
12479                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12480                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12481                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12482                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12483                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12484                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12485                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12486                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12487                         { quote = "# "; print = (appData.engineComments == 2); }
12488                 message[0] = start; // restore original message
12489         }
12490         if(print) {
12491                 GetTimeMark(&now);
12492                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12493                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12494                         quote,
12495                         message);
12496         }
12497     }
12498
12499     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12500     if (appData.icsEngineAnalyze) {
12501         if (strstr(message, "whisper") != NULL ||
12502              strstr(message, "kibitz") != NULL || 
12503             strstr(message, "tellics") != NULL) return;
12504     }
12505
12506     HandleMachineMove(message, cps);
12507 }
12508
12509
12510 void
12511 SendTimeControl(cps, mps, tc, inc, sd, st)
12512      ChessProgramState *cps;
12513      int mps, inc, sd, st;
12514      long tc;
12515 {
12516     char buf[MSG_SIZ];
12517     int seconds;
12518
12519     if( timeControl_2 > 0 ) {
12520         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12521             tc = timeControl_2;
12522         }
12523     }
12524     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12525     inc /= cps->timeOdds;
12526     st  /= cps->timeOdds;
12527
12528     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12529
12530     if (st > 0) {
12531       /* Set exact time per move, normally using st command */
12532       if (cps->stKludge) {
12533         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12534         seconds = st % 60;
12535         if (seconds == 0) {
12536           sprintf(buf, "level 1 %d\n", st/60);
12537         } else {
12538           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12539         }
12540       } else {
12541         sprintf(buf, "st %d\n", st);
12542       }
12543     } else {
12544       /* Set conventional or incremental time control, using level command */
12545       if (seconds == 0) {
12546         /* Note old gnuchess bug -- minutes:seconds used to not work.
12547            Fixed in later versions, but still avoid :seconds
12548            when seconds is 0. */
12549         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12550       } else {
12551         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12552                 seconds, inc/1000);
12553       }
12554     }
12555     SendToProgram(buf, cps);
12556
12557     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12558     /* Orthogonally, limit search to given depth */
12559     if (sd > 0) {
12560       if (cps->sdKludge) {
12561         sprintf(buf, "depth\n%d\n", sd);
12562       } else {
12563         sprintf(buf, "sd %d\n", sd);
12564       }
12565       SendToProgram(buf, cps);
12566     }
12567
12568     if(cps->nps > 0) { /* [HGM] nps */
12569         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12570         else {
12571                 sprintf(buf, "nps %d\n", cps->nps);
12572               SendToProgram(buf, cps);
12573         }
12574     }
12575 }
12576
12577 ChessProgramState *WhitePlayer()
12578 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12579 {
12580     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12581        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12582         return &second;
12583     return &first;
12584 }
12585
12586 void
12587 SendTimeRemaining(cps, machineWhite)
12588      ChessProgramState *cps;
12589      int /*boolean*/ machineWhite;
12590 {
12591     char message[MSG_SIZ];
12592     long time, otime;
12593
12594     /* Note: this routine must be called when the clocks are stopped
12595        or when they have *just* been set or switched; otherwise
12596        it will be off by the time since the current tick started.
12597     */
12598     if (machineWhite) {
12599         time = whiteTimeRemaining / 10;
12600         otime = blackTimeRemaining / 10;
12601     } else {
12602         time = blackTimeRemaining / 10;
12603         otime = whiteTimeRemaining / 10;
12604     }
12605     /* [HGM] translate opponent's time by time-odds factor */
12606     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12607     if (appData.debugMode) {
12608         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12609     }
12610
12611     if (time <= 0) time = 1;
12612     if (otime <= 0) otime = 1;
12613     
12614     sprintf(message, "time %ld\n", time);
12615     SendToProgram(message, cps);
12616
12617     sprintf(message, "otim %ld\n", otime);
12618     SendToProgram(message, cps);
12619 }
12620
12621 int
12622 BoolFeature(p, name, loc, cps)
12623      char **p;
12624      char *name;
12625      int *loc;
12626      ChessProgramState *cps;
12627 {
12628   char buf[MSG_SIZ];
12629   int len = strlen(name);
12630   int val;
12631   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12632     (*p) += len + 1;
12633     sscanf(*p, "%d", &val);
12634     *loc = (val != 0);
12635     while (**p && **p != ' ') (*p)++;
12636     sprintf(buf, "accepted %s\n", name);
12637     SendToProgram(buf, cps);
12638     return TRUE;
12639   }
12640   return FALSE;
12641 }
12642
12643 int
12644 IntFeature(p, name, loc, cps)
12645      char **p;
12646      char *name;
12647      int *loc;
12648      ChessProgramState *cps;
12649 {
12650   char buf[MSG_SIZ];
12651   int len = strlen(name);
12652   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12653     (*p) += len + 1;
12654     sscanf(*p, "%d", loc);
12655     while (**p && **p != ' ') (*p)++;
12656     sprintf(buf, "accepted %s\n", name);
12657     SendToProgram(buf, cps);
12658     return TRUE;
12659   }
12660   return FALSE;
12661 }
12662
12663 int
12664 StringFeature(p, name, loc, cps)
12665      char **p;
12666      char *name;
12667      char loc[];
12668      ChessProgramState *cps;
12669 {
12670   char buf[MSG_SIZ];
12671   int len = strlen(name);
12672   if (strncmp((*p), name, len) == 0
12673       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12674     (*p) += len + 2;
12675     sscanf(*p, "%[^\"]", loc);
12676     while (**p && **p != '\"') (*p)++;
12677     if (**p == '\"') (*p)++;
12678     sprintf(buf, "accepted %s\n", name);
12679     SendToProgram(buf, cps);
12680     return TRUE;
12681   }
12682   return FALSE;
12683 }
12684
12685 int 
12686 ParseOption(Option *opt, ChessProgramState *cps)
12687 // [HGM] options: process the string that defines an engine option, and determine
12688 // name, type, default value, and allowed value range
12689 {
12690         char *p, *q, buf[MSG_SIZ];
12691         int n, min = (-1)<<31, max = 1<<31, def;
12692
12693         if(p = strstr(opt->name, " -spin ")) {
12694             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12695             if(max < min) max = min; // enforce consistency
12696             if(def < min) def = min;
12697             if(def > max) def = max;
12698             opt->value = def;
12699             opt->min = min;
12700             opt->max = max;
12701             opt->type = Spin;
12702         } else if((p = strstr(opt->name, " -slider "))) {
12703             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12704             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12705             if(max < min) max = min; // enforce consistency
12706             if(def < min) def = min;
12707             if(def > max) def = max;
12708             opt->value = def;
12709             opt->min = min;
12710             opt->max = max;
12711             opt->type = Spin; // Slider;
12712         } else if((p = strstr(opt->name, " -string "))) {
12713             opt->textValue = p+9;
12714             opt->type = TextBox;
12715         } else if((p = strstr(opt->name, " -file "))) {
12716             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12717             opt->textValue = p+7;
12718             opt->type = TextBox; // FileName;
12719         } else if((p = strstr(opt->name, " -path "))) {
12720             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12721             opt->textValue = p+7;
12722             opt->type = TextBox; // PathName;
12723         } else if(p = strstr(opt->name, " -check ")) {
12724             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12725             opt->value = (def != 0);
12726             opt->type = CheckBox;
12727         } else if(p = strstr(opt->name, " -combo ")) {
12728             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12729             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12730             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12731             opt->value = n = 0;
12732             while(q = StrStr(q, " /// ")) {
12733                 n++; *q = 0;    // count choices, and null-terminate each of them
12734                 q += 5;
12735                 if(*q == '*') { // remember default, which is marked with * prefix
12736                     q++;
12737                     opt->value = n;
12738                 }
12739                 cps->comboList[cps->comboCnt++] = q;
12740             }
12741             cps->comboList[cps->comboCnt++] = NULL;
12742             opt->max = n + 1;
12743             opt->type = ComboBox;
12744         } else if(p = strstr(opt->name, " -button")) {
12745             opt->type = Button;
12746         } else if(p = strstr(opt->name, " -save")) {
12747             opt->type = SaveButton;
12748         } else return FALSE;
12749         *p = 0; // terminate option name
12750         // now look if the command-line options define a setting for this engine option.
12751         if(cps->optionSettings && cps->optionSettings[0])
12752             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12753         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12754                 sprintf(buf, "option %s", p);
12755                 if(p = strstr(buf, ",")) *p = 0;
12756                 strcat(buf, "\n");
12757                 SendToProgram(buf, cps);
12758         }
12759         return TRUE;
12760 }
12761
12762 void
12763 FeatureDone(cps, val)
12764      ChessProgramState* cps;
12765      int val;
12766 {
12767   DelayedEventCallback cb = GetDelayedEvent();
12768   if ((cb == InitBackEnd3 && cps == &first) ||
12769       (cb == TwoMachinesEventIfReady && cps == &second)) {
12770     CancelDelayedEvent();
12771     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12772   }
12773   cps->initDone = val;
12774 }
12775
12776 /* Parse feature command from engine */
12777 void
12778 ParseFeatures(args, cps)
12779      char* args;
12780      ChessProgramState *cps;  
12781 {
12782   char *p = args;
12783   char *q;
12784   int val;
12785   char buf[MSG_SIZ];
12786
12787   for (;;) {
12788     while (*p == ' ') p++;
12789     if (*p == NULLCHAR) return;
12790
12791     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12792     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12793     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12794     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12795     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12796     if (BoolFeature(&p, "reuse", &val, cps)) {
12797       /* Engine can disable reuse, but can't enable it if user said no */
12798       if (!val) cps->reuse = FALSE;
12799       continue;
12800     }
12801     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12802     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12803       if (gameMode == TwoMachinesPlay) {
12804         DisplayTwoMachinesTitle();
12805       } else {
12806         DisplayTitle("");
12807       }
12808       continue;
12809     }
12810     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12811     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12812     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12813     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12814     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12815     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12816     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12817     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12818     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12819     if (IntFeature(&p, "done", &val, cps)) {
12820       FeatureDone(cps, val);
12821       continue;
12822     }
12823     /* Added by Tord: */
12824     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12825     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12826     /* End of additions by Tord */
12827
12828     /* [HGM] added features: */
12829     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12830     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12831     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12832     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12833     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12834     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12835     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12836         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12837             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12838             SendToProgram(buf, cps);
12839             continue;
12840         }
12841         if(cps->nrOptions >= MAX_OPTIONS) {
12842             cps->nrOptions--;
12843             sprintf(buf, "%s engine has too many options\n", cps->which);
12844             DisplayError(buf, 0);
12845         }
12846         continue;
12847     }
12848     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12849     /* End of additions by HGM */
12850
12851     /* unknown feature: complain and skip */
12852     q = p;
12853     while (*q && *q != '=') q++;
12854     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12855     SendToProgram(buf, cps);
12856     p = q;
12857     if (*p == '=') {
12858       p++;
12859       if (*p == '\"') {
12860         p++;
12861         while (*p && *p != '\"') p++;
12862         if (*p == '\"') p++;
12863       } else {
12864         while (*p && *p != ' ') p++;
12865       }
12866     }
12867   }
12868
12869 }
12870
12871 void
12872 PeriodicUpdatesEvent(newState)
12873      int newState;
12874 {
12875     if (newState == appData.periodicUpdates)
12876       return;
12877
12878     appData.periodicUpdates=newState;
12879
12880     /* Display type changes, so update it now */
12881 //    DisplayAnalysis();
12882
12883     /* Get the ball rolling again... */
12884     if (newState) {
12885         AnalysisPeriodicEvent(1);
12886         StartAnalysisClock();
12887     }
12888 }
12889
12890 void
12891 PonderNextMoveEvent(newState)
12892      int newState;
12893 {
12894     if (newState == appData.ponderNextMove) return;
12895     if (gameMode == EditPosition) EditPositionDone();
12896     if (newState) {
12897         SendToProgram("hard\n", &first);
12898         if (gameMode == TwoMachinesPlay) {
12899             SendToProgram("hard\n", &second);
12900         }
12901     } else {
12902         SendToProgram("easy\n", &first);
12903         thinkOutput[0] = NULLCHAR;
12904         if (gameMode == TwoMachinesPlay) {
12905             SendToProgram("easy\n", &second);
12906         }
12907     }
12908     appData.ponderNextMove = newState;
12909 }
12910
12911 void
12912 NewSettingEvent(option, command, value)
12913      char *command;
12914      int option, value;
12915 {
12916     char buf[MSG_SIZ];
12917
12918     if (gameMode == EditPosition) EditPositionDone();
12919     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12920     SendToProgram(buf, &first);
12921     if (gameMode == TwoMachinesPlay) {
12922         SendToProgram(buf, &second);
12923     }
12924 }
12925
12926 void
12927 ShowThinkingEvent()
12928 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12929 {
12930     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12931     int newState = appData.showThinking
12932         // [HGM] thinking: other features now need thinking output as well
12933         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12934     
12935     if (oldState == newState) return;
12936     oldState = newState;
12937     if (gameMode == EditPosition) EditPositionDone();
12938     if (oldState) {
12939         SendToProgram("post\n", &first);
12940         if (gameMode == TwoMachinesPlay) {
12941             SendToProgram("post\n", &second);
12942         }
12943     } else {
12944         SendToProgram("nopost\n", &first);
12945         thinkOutput[0] = NULLCHAR;
12946         if (gameMode == TwoMachinesPlay) {
12947             SendToProgram("nopost\n", &second);
12948         }
12949     }
12950 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12951 }
12952
12953 void
12954 AskQuestionEvent(title, question, replyPrefix, which)
12955      char *title; char *question; char *replyPrefix; char *which;
12956 {
12957   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12958   if (pr == NoProc) return;
12959   AskQuestion(title, question, replyPrefix, pr);
12960 }
12961
12962 void
12963 DisplayMove(moveNumber)
12964      int moveNumber;
12965 {
12966     char message[MSG_SIZ];
12967     char res[MSG_SIZ];
12968     char cpThinkOutput[MSG_SIZ];
12969
12970     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12971     
12972     if (moveNumber == forwardMostMove - 1 || 
12973         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12974
12975         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12976
12977         if (strchr(cpThinkOutput, '\n')) {
12978             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12979         }
12980     } else {
12981         *cpThinkOutput = NULLCHAR;
12982     }
12983
12984     /* [AS] Hide thinking from human user */
12985     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12986         *cpThinkOutput = NULLCHAR;
12987         if( thinkOutput[0] != NULLCHAR ) {
12988             int i;
12989
12990             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12991                 cpThinkOutput[i] = '.';
12992             }
12993             cpThinkOutput[i] = NULLCHAR;
12994             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12995         }
12996     }
12997
12998     if (moveNumber == forwardMostMove - 1 &&
12999         gameInfo.resultDetails != NULL) {
13000         if (gameInfo.resultDetails[0] == NULLCHAR) {
13001             sprintf(res, " %s", PGNResult(gameInfo.result));
13002         } else {
13003             sprintf(res, " {%s} %s",
13004                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13005         }
13006     } else {
13007         res[0] = NULLCHAR;
13008     }
13009
13010     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13011         DisplayMessage(res, cpThinkOutput);
13012     } else {
13013         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13014                 WhiteOnMove(moveNumber) ? " " : ".. ",
13015                 parseList[moveNumber], res);
13016         DisplayMessage(message, cpThinkOutput);
13017     }
13018 }
13019
13020 void
13021 DisplayComment(moveNumber, text)
13022      int moveNumber;
13023      char *text;
13024 {
13025     char title[MSG_SIZ];
13026     char buf[8000]; // comment can be long!
13027     int score, depth;
13028     
13029     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13030       strcpy(title, "Comment");
13031     } else {
13032       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13033               WhiteOnMove(moveNumber) ? " " : ".. ",
13034               parseList[moveNumber]);
13035     }
13036     // [HGM] PV info: display PV info together with (or as) comment
13037     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13038       if(text == NULL) text = "";                                           
13039       score = pvInfoList[moveNumber].score;
13040       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13041               depth, (pvInfoList[moveNumber].time+50)/100, text);
13042       text = buf;
13043     }
13044     if (text != NULL && (appData.autoDisplayComment || commentUp))
13045         CommentPopUp(title, text);
13046 }
13047
13048 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13049  * might be busy thinking or pondering.  It can be omitted if your
13050  * gnuchess is configured to stop thinking immediately on any user
13051  * input.  However, that gnuchess feature depends on the FIONREAD
13052  * ioctl, which does not work properly on some flavors of Unix.
13053  */
13054 void
13055 Attention(cps)
13056      ChessProgramState *cps;
13057 {
13058 #if ATTENTION
13059     if (!cps->useSigint) return;
13060     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13061     switch (gameMode) {
13062       case MachinePlaysWhite:
13063       case MachinePlaysBlack:
13064       case TwoMachinesPlay:
13065       case IcsPlayingWhite:
13066       case IcsPlayingBlack:
13067       case AnalyzeMode:
13068       case AnalyzeFile:
13069         /* Skip if we know it isn't thinking */
13070         if (!cps->maybeThinking) return;
13071         if (appData.debugMode)
13072           fprintf(debugFP, "Interrupting %s\n", cps->which);
13073         InterruptChildProcess(cps->pr);
13074         cps->maybeThinking = FALSE;
13075         break;
13076       default:
13077         break;
13078     }
13079 #endif /*ATTENTION*/
13080 }
13081
13082 int
13083 CheckFlags()
13084 {
13085     if (whiteTimeRemaining <= 0) {
13086         if (!whiteFlag) {
13087             whiteFlag = TRUE;
13088             if (appData.icsActive) {
13089                 if (appData.autoCallFlag &&
13090                     gameMode == IcsPlayingBlack && !blackFlag) {
13091                   SendToICS(ics_prefix);
13092                   SendToICS("flag\n");
13093                 }
13094             } else {
13095                 if (blackFlag) {
13096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13097                 } else {
13098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13099                     if (appData.autoCallFlag) {
13100                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13101                         return TRUE;
13102                     }
13103                 }
13104             }
13105         }
13106     }
13107     if (blackTimeRemaining <= 0) {
13108         if (!blackFlag) {
13109             blackFlag = TRUE;
13110             if (appData.icsActive) {
13111                 if (appData.autoCallFlag &&
13112                     gameMode == IcsPlayingWhite && !whiteFlag) {
13113                   SendToICS(ics_prefix);
13114                   SendToICS("flag\n");
13115                 }
13116             } else {
13117                 if (whiteFlag) {
13118                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13119                 } else {
13120                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13121                     if (appData.autoCallFlag) {
13122                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13123                         return TRUE;
13124                     }
13125                 }
13126             }
13127         }
13128     }
13129     return FALSE;
13130 }
13131
13132 void
13133 CheckTimeControl()
13134 {
13135     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13136         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13137
13138     /*
13139      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13140      */
13141     if ( !WhiteOnMove(forwardMostMove) )
13142         /* White made time control */
13143         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13144         /* [HGM] time odds: correct new time quota for time odds! */
13145                                             / WhitePlayer()->timeOdds;
13146       else
13147         /* Black made time control */
13148         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13149                                             / WhitePlayer()->other->timeOdds;
13150 }
13151
13152 void
13153 DisplayBothClocks()
13154 {
13155     int wom = gameMode == EditPosition ?
13156       !blackPlaysFirst : WhiteOnMove(currentMove);
13157     DisplayWhiteClock(whiteTimeRemaining, wom);
13158     DisplayBlackClock(blackTimeRemaining, !wom);
13159 }
13160
13161
13162 /* Timekeeping seems to be a portability nightmare.  I think everyone
13163    has ftime(), but I'm really not sure, so I'm including some ifdefs
13164    to use other calls if you don't.  Clocks will be less accurate if
13165    you have neither ftime nor gettimeofday.
13166 */
13167
13168 /* VS 2008 requires the #include outside of the function */
13169 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13170 #include <sys/timeb.h>
13171 #endif
13172
13173 /* Get the current time as a TimeMark */
13174 void
13175 GetTimeMark(tm)
13176      TimeMark *tm;
13177 {
13178 #if HAVE_GETTIMEOFDAY
13179
13180     struct timeval timeVal;
13181     struct timezone timeZone;
13182
13183     gettimeofday(&timeVal, &timeZone);
13184     tm->sec = (long) timeVal.tv_sec; 
13185     tm->ms = (int) (timeVal.tv_usec / 1000L);
13186
13187 #else /*!HAVE_GETTIMEOFDAY*/
13188 #if HAVE_FTIME
13189
13190 // include <sys/timeb.h> / moved to just above start of function
13191     struct timeb timeB;
13192
13193     ftime(&timeB);
13194     tm->sec = (long) timeB.time;
13195     tm->ms = (int) timeB.millitm;
13196
13197 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13198     tm->sec = (long) time(NULL);
13199     tm->ms = 0;
13200 #endif
13201 #endif
13202 }
13203
13204 /* Return the difference in milliseconds between two
13205    time marks.  We assume the difference will fit in a long!
13206 */
13207 long
13208 SubtractTimeMarks(tm2, tm1)
13209      TimeMark *tm2, *tm1;
13210 {
13211     return 1000L*(tm2->sec - tm1->sec) +
13212            (long) (tm2->ms - tm1->ms);
13213 }
13214
13215
13216 /*
13217  * Code to manage the game clocks.
13218  *
13219  * In tournament play, black starts the clock and then white makes a move.
13220  * We give the human user a slight advantage if he is playing white---the
13221  * clocks don't run until he makes his first move, so it takes zero time.
13222  * Also, we don't account for network lag, so we could get out of sync
13223  * with GNU Chess's clock -- but then, referees are always right.  
13224  */
13225
13226 static TimeMark tickStartTM;
13227 static long intendedTickLength;
13228
13229 long
13230 NextTickLength(timeRemaining)
13231      long timeRemaining;
13232 {
13233     long nominalTickLength, nextTickLength;
13234
13235     if (timeRemaining > 0L && timeRemaining <= 10000L)
13236       nominalTickLength = 100L;
13237     else
13238       nominalTickLength = 1000L;
13239     nextTickLength = timeRemaining % nominalTickLength;
13240     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13241
13242     return nextTickLength;
13243 }
13244
13245 /* Adjust clock one minute up or down */
13246 void
13247 AdjustClock(Boolean which, int dir)
13248 {
13249     if(which) blackTimeRemaining += 60000*dir;
13250     else      whiteTimeRemaining += 60000*dir;
13251     DisplayBothClocks();
13252 }
13253
13254 /* Stop clocks and reset to a fresh time control */
13255 void
13256 ResetClocks() 
13257 {
13258     (void) StopClockTimer();
13259     if (appData.icsActive) {
13260         whiteTimeRemaining = blackTimeRemaining = 0;
13261     } else if (searchTime) {
13262         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13263         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13264     } else { /* [HGM] correct new time quote for time odds */
13265         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13266         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13267     }
13268     if (whiteFlag || blackFlag) {
13269         DisplayTitle("");
13270         whiteFlag = blackFlag = FALSE;
13271     }
13272     DisplayBothClocks();
13273 }
13274
13275 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13276
13277 /* Decrement running clock by amount of time that has passed */
13278 void
13279 DecrementClocks()
13280 {
13281     long timeRemaining;
13282     long lastTickLength, fudge;
13283     TimeMark now;
13284
13285     if (!appData.clockMode) return;
13286     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13287         
13288     GetTimeMark(&now);
13289
13290     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13291
13292     /* Fudge if we woke up a little too soon */
13293     fudge = intendedTickLength - lastTickLength;
13294     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13295
13296     if (WhiteOnMove(forwardMostMove)) {
13297         if(whiteNPS >= 0) lastTickLength = 0;
13298         timeRemaining = whiteTimeRemaining -= lastTickLength;
13299         DisplayWhiteClock(whiteTimeRemaining - fudge,
13300                           WhiteOnMove(currentMove));
13301     } else {
13302         if(blackNPS >= 0) lastTickLength = 0;
13303         timeRemaining = blackTimeRemaining -= lastTickLength;
13304         DisplayBlackClock(blackTimeRemaining - fudge,
13305                           !WhiteOnMove(currentMove));
13306     }
13307
13308     if (CheckFlags()) return;
13309         
13310     tickStartTM = now;
13311     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13312     StartClockTimer(intendedTickLength);
13313
13314     /* if the time remaining has fallen below the alarm threshold, sound the
13315      * alarm. if the alarm has sounded and (due to a takeback or time control
13316      * with increment) the time remaining has increased to a level above the
13317      * threshold, reset the alarm so it can sound again. 
13318      */
13319     
13320     if (appData.icsActive && appData.icsAlarm) {
13321
13322         /* make sure we are dealing with the user's clock */
13323         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13324                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13325            )) return;
13326
13327         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13328             alarmSounded = FALSE;
13329         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13330             PlayAlarmSound();
13331             alarmSounded = TRUE;
13332         }
13333     }
13334 }
13335
13336
13337 /* A player has just moved, so stop the previously running
13338    clock and (if in clock mode) start the other one.
13339    We redisplay both clocks in case we're in ICS mode, because
13340    ICS gives us an update to both clocks after every move.
13341    Note that this routine is called *after* forwardMostMove
13342    is updated, so the last fractional tick must be subtracted
13343    from the color that is *not* on move now.
13344 */
13345 void
13346 SwitchClocks()
13347 {
13348     long lastTickLength;
13349     TimeMark now;
13350     int flagged = FALSE;
13351
13352     GetTimeMark(&now);
13353
13354     if (StopClockTimer() && appData.clockMode) {
13355         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13356         if (WhiteOnMove(forwardMostMove)) {
13357             if(blackNPS >= 0) lastTickLength = 0;
13358             blackTimeRemaining -= lastTickLength;
13359            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13360 //         if(pvInfoList[forwardMostMove-1].time == -1)
13361                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13362                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13363         } else {
13364            if(whiteNPS >= 0) lastTickLength = 0;
13365            whiteTimeRemaining -= lastTickLength;
13366            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13367 //         if(pvInfoList[forwardMostMove-1].time == -1)
13368                  pvInfoList[forwardMostMove-1].time = 
13369                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13370         }
13371         flagged = CheckFlags();
13372     }
13373     CheckTimeControl();
13374
13375     if (flagged || !appData.clockMode) return;
13376
13377     switch (gameMode) {
13378       case MachinePlaysBlack:
13379       case MachinePlaysWhite:
13380       case BeginningOfGame:
13381         if (pausing) return;
13382         break;
13383
13384       case EditGame:
13385       case PlayFromGameFile:
13386       case IcsExamining:
13387         return;
13388
13389       default:
13390         break;
13391     }
13392
13393     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13394         if(WhiteOnMove(forwardMostMove))
13395              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13396         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13397     }
13398
13399     tickStartTM = now;
13400     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13401       whiteTimeRemaining : blackTimeRemaining);
13402     StartClockTimer(intendedTickLength);
13403 }
13404         
13405
13406 /* Stop both clocks */
13407 void
13408 StopClocks()
13409 {       
13410     long lastTickLength;
13411     TimeMark now;
13412
13413     if (!StopClockTimer()) return;
13414     if (!appData.clockMode) return;
13415
13416     GetTimeMark(&now);
13417
13418     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13419     if (WhiteOnMove(forwardMostMove)) {
13420         if(whiteNPS >= 0) lastTickLength = 0;
13421         whiteTimeRemaining -= lastTickLength;
13422         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13423     } else {
13424         if(blackNPS >= 0) lastTickLength = 0;
13425         blackTimeRemaining -= lastTickLength;
13426         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13427     }
13428     CheckFlags();
13429 }
13430         
13431 /* Start clock of player on move.  Time may have been reset, so
13432    if clock is already running, stop and restart it. */
13433 void
13434 StartClocks()
13435 {
13436     (void) StopClockTimer(); /* in case it was running already */
13437     DisplayBothClocks();
13438     if (CheckFlags()) return;
13439
13440     if (!appData.clockMode) return;
13441     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13442
13443     GetTimeMark(&tickStartTM);
13444     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13445       whiteTimeRemaining : blackTimeRemaining);
13446
13447    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13448     whiteNPS = blackNPS = -1; 
13449     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13450        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13451         whiteNPS = first.nps;
13452     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13453        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13454         blackNPS = first.nps;
13455     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13456         whiteNPS = second.nps;
13457     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13458         blackNPS = second.nps;
13459     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13460
13461     StartClockTimer(intendedTickLength);
13462 }
13463
13464 char *
13465 TimeString(ms)
13466      long ms;
13467 {
13468     long second, minute, hour, day;
13469     char *sign = "";
13470     static char buf[32];
13471     
13472     if (ms > 0 && ms <= 9900) {
13473       /* convert milliseconds to tenths, rounding up */
13474       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13475
13476       sprintf(buf, " %03.1f ", tenths/10.0);
13477       return buf;
13478     }
13479
13480     /* convert milliseconds to seconds, rounding up */
13481     /* use floating point to avoid strangeness of integer division
13482        with negative dividends on many machines */
13483     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13484
13485     if (second < 0) {
13486         sign = "-";
13487         second = -second;
13488     }
13489     
13490     day = second / (60 * 60 * 24);
13491     second = second % (60 * 60 * 24);
13492     hour = second / (60 * 60);
13493     second = second % (60 * 60);
13494     minute = second / 60;
13495     second = second % 60;
13496     
13497     if (day > 0)
13498       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13499               sign, day, hour, minute, second);
13500     else if (hour > 0)
13501       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13502     else
13503       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13504     
13505     return buf;
13506 }
13507
13508
13509 /*
13510  * This is necessary because some C libraries aren't ANSI C compliant yet.
13511  */
13512 char *
13513 StrStr(string, match)
13514      char *string, *match;
13515 {
13516     int i, length;
13517     
13518     length = strlen(match);
13519     
13520     for (i = strlen(string) - length; i >= 0; i--, string++)
13521       if (!strncmp(match, string, length))
13522         return string;
13523     
13524     return NULL;
13525 }
13526
13527 char *
13528 StrCaseStr(string, match)
13529      char *string, *match;
13530 {
13531     int i, j, length;
13532     
13533     length = strlen(match);
13534     
13535     for (i = strlen(string) - length; i >= 0; i--, string++) {
13536         for (j = 0; j < length; j++) {
13537             if (ToLower(match[j]) != ToLower(string[j]))
13538               break;
13539         }
13540         if (j == length) return string;
13541     }
13542
13543     return NULL;
13544 }
13545
13546 #ifndef _amigados
13547 int
13548 StrCaseCmp(s1, s2)
13549      char *s1, *s2;
13550 {
13551     char c1, c2;
13552     
13553     for (;;) {
13554         c1 = ToLower(*s1++);
13555         c2 = ToLower(*s2++);
13556         if (c1 > c2) return 1;
13557         if (c1 < c2) return -1;
13558         if (c1 == NULLCHAR) return 0;
13559     }
13560 }
13561
13562
13563 int
13564 ToLower(c)
13565      int c;
13566 {
13567     return isupper(c) ? tolower(c) : c;
13568 }
13569
13570
13571 int
13572 ToUpper(c)
13573      int c;
13574 {
13575     return islower(c) ? toupper(c) : c;
13576 }
13577 #endif /* !_amigados    */
13578
13579 char *
13580 StrSave(s)
13581      char *s;
13582 {
13583     char *ret;
13584
13585     if ((ret = (char *) malloc(strlen(s) + 1))) {
13586         strcpy(ret, s);
13587     }
13588     return ret;
13589 }
13590
13591 char *
13592 StrSavePtr(s, savePtr)
13593      char *s, **savePtr;
13594 {
13595     if (*savePtr) {
13596         free(*savePtr);
13597     }
13598     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13599         strcpy(*savePtr, s);
13600     }
13601     return(*savePtr);
13602 }
13603
13604 char *
13605 PGNDate()
13606 {
13607     time_t clock;
13608     struct tm *tm;
13609     char buf[MSG_SIZ];
13610
13611     clock = time((time_t *)NULL);
13612     tm = localtime(&clock);
13613     sprintf(buf, "%04d.%02d.%02d",
13614             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13615     return StrSave(buf);
13616 }
13617
13618
13619 char *
13620 PositionToFEN(move, overrideCastling)
13621      int move;
13622      char *overrideCastling;
13623 {
13624     int i, j, fromX, fromY, toX, toY;
13625     int whiteToPlay;
13626     char buf[128];
13627     char *p, *q;
13628     int emptycount;
13629     ChessSquare piece;
13630
13631     whiteToPlay = (gameMode == EditPosition) ?
13632       !blackPlaysFirst : (move % 2 == 0);
13633     p = buf;
13634
13635     /* Piece placement data */
13636     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13637         emptycount = 0;
13638         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13639             if (boards[move][i][j] == EmptySquare) {
13640                 emptycount++;
13641             } else { ChessSquare piece = boards[move][i][j];
13642                 if (emptycount > 0) {
13643                     if(emptycount<10) /* [HGM] can be >= 10 */
13644                         *p++ = '0' + emptycount;
13645                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13646                     emptycount = 0;
13647                 }
13648                 if(PieceToChar(piece) == '+') {
13649                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13650                     *p++ = '+';
13651                     piece = (ChessSquare)(DEMOTED piece);
13652                 } 
13653                 *p++ = PieceToChar(piece);
13654                 if(p[-1] == '~') {
13655                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13656                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13657                     *p++ = '~';
13658                 }
13659             }
13660         }
13661         if (emptycount > 0) {
13662             if(emptycount<10) /* [HGM] can be >= 10 */
13663                 *p++ = '0' + emptycount;
13664             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13665             emptycount = 0;
13666         }
13667         *p++ = '/';
13668     }
13669     *(p - 1) = ' ';
13670
13671     /* [HGM] print Crazyhouse or Shogi holdings */
13672     if( gameInfo.holdingsWidth ) {
13673         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13674         q = p;
13675         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13676             piece = boards[move][i][BOARD_WIDTH-1];
13677             if( piece != EmptySquare )
13678               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13679                   *p++ = PieceToChar(piece);
13680         }
13681         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13682             piece = boards[move][BOARD_HEIGHT-i-1][0];
13683             if( piece != EmptySquare )
13684               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13685                   *p++ = PieceToChar(piece);
13686         }
13687
13688         if( q == p ) *p++ = '-';
13689         *p++ = ']';
13690         *p++ = ' ';
13691     }
13692
13693     /* Active color */
13694     *p++ = whiteToPlay ? 'w' : 'b';
13695     *p++ = ' ';
13696
13697   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13698     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13699   } else {
13700   if(nrCastlingRights) {
13701      q = p;
13702      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13703        /* [HGM] write directly from rights */
13704            if(castlingRights[move][2] >= 0 &&
13705               castlingRights[move][0] >= 0   )
13706                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13707            if(castlingRights[move][2] >= 0 &&
13708               castlingRights[move][1] >= 0   )
13709                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13710            if(castlingRights[move][5] >= 0 &&
13711               castlingRights[move][3] >= 0   )
13712                 *p++ = castlingRights[move][3] + AAA;
13713            if(castlingRights[move][5] >= 0 &&
13714               castlingRights[move][4] >= 0   )
13715                 *p++ = castlingRights[move][4] + AAA;
13716      } else {
13717
13718         /* [HGM] write true castling rights */
13719         if( nrCastlingRights == 6 ) {
13720             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13721                castlingRights[move][2] >= 0  ) *p++ = 'K';
13722             if(castlingRights[move][1] == BOARD_LEFT &&
13723                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13724             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13725                castlingRights[move][5] >= 0  ) *p++ = 'k';
13726             if(castlingRights[move][4] == BOARD_LEFT &&
13727                castlingRights[move][5] >= 0  ) *p++ = 'q';
13728         }
13729      }
13730      if (q == p) *p++ = '-'; /* No castling rights */
13731      *p++ = ' ';
13732   }
13733
13734   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13735      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13736     /* En passant target square */
13737     if (move > backwardMostMove) {
13738         fromX = moveList[move - 1][0] - AAA;
13739         fromY = moveList[move - 1][1] - ONE;
13740         toX = moveList[move - 1][2] - AAA;
13741         toY = moveList[move - 1][3] - ONE;
13742         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13743             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13744             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13745             fromX == toX) {
13746             /* 2-square pawn move just happened */
13747             *p++ = toX + AAA;
13748             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13749         } else {
13750             *p++ = '-';
13751         }
13752     } else if(move == backwardMostMove) {
13753         // [HGM] perhaps we should always do it like this, and forget the above?
13754         if(epStatus[move] >= 0) {
13755             *p++ = epStatus[move] + AAA;
13756             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13757         } else {
13758             *p++ = '-';
13759         }
13760     } else {
13761         *p++ = '-';
13762     }
13763     *p++ = ' ';
13764   }
13765   }
13766
13767     /* [HGM] find reversible plies */
13768     {   int i = 0, j=move;
13769
13770         if (appData.debugMode) { int k;
13771             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13772             for(k=backwardMostMove; k<=forwardMostMove; k++)
13773                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13774
13775         }
13776
13777         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13778         if( j == backwardMostMove ) i += initialRulePlies;
13779         sprintf(p, "%d ", i);
13780         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13781     }
13782     /* Fullmove number */
13783     sprintf(p, "%d", (move / 2) + 1);
13784     
13785     return StrSave(buf);
13786 }
13787
13788 Boolean
13789 ParseFEN(board, blackPlaysFirst, fen)
13790     Board board;
13791      int *blackPlaysFirst;
13792      char *fen;
13793 {
13794     int i, j;
13795     char *p;
13796     int emptycount;
13797     ChessSquare piece;
13798
13799     p = fen;
13800
13801     /* [HGM] by default clear Crazyhouse holdings, if present */
13802     if(gameInfo.holdingsWidth) {
13803        for(i=0; i<BOARD_HEIGHT; i++) {
13804            board[i][0]             = EmptySquare; /* black holdings */
13805            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13806            board[i][1]             = (ChessSquare) 0; /* black counts */
13807            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13808        }
13809     }
13810
13811     /* Piece placement data */
13812     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13813         j = 0;
13814         for (;;) {
13815             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13816                 if (*p == '/') p++;
13817                 emptycount = gameInfo.boardWidth - j;
13818                 while (emptycount--)
13819                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13820                 break;
13821 #if(BOARD_SIZE >= 10)
13822             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13823                 p++; emptycount=10;
13824                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13825                 while (emptycount--)
13826                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13827 #endif
13828             } else if (isdigit(*p)) {
13829                 emptycount = *p++ - '0';
13830                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13831                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13832                 while (emptycount--)
13833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13834             } else if (*p == '+' || isalpha(*p)) {
13835                 if (j >= gameInfo.boardWidth) return FALSE;
13836                 if(*p=='+') {
13837                     piece = CharToPiece(*++p);
13838                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13839                     piece = (ChessSquare) (PROMOTED piece ); p++;
13840                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13841                 } else piece = CharToPiece(*p++);
13842
13843                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13844                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13845                     piece = (ChessSquare) (PROMOTED piece);
13846                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13847                     p++;
13848                 }
13849                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13850             } else {
13851                 return FALSE;
13852             }
13853         }
13854     }
13855     while (*p == '/' || *p == ' ') p++;
13856
13857     /* [HGM] look for Crazyhouse holdings here */
13858     while(*p==' ') p++;
13859     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13860         if(*p == '[') p++;
13861         if(*p == '-' ) *p++; /* empty holdings */ else {
13862             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13863             /* if we would allow FEN reading to set board size, we would   */
13864             /* have to add holdings and shift the board read so far here   */
13865             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13866                 *p++;
13867                 if((int) piece >= (int) BlackPawn ) {
13868                     i = (int)piece - (int)BlackPawn;
13869                     i = PieceToNumber((ChessSquare)i);
13870                     if( i >= gameInfo.holdingsSize ) return FALSE;
13871                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13872                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13873                 } else {
13874                     i = (int)piece - (int)WhitePawn;
13875                     i = PieceToNumber((ChessSquare)i);
13876                     if( i >= gameInfo.holdingsSize ) return FALSE;
13877                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13878                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13879                 }
13880             }
13881         }
13882         if(*p == ']') *p++;
13883     }
13884
13885     while(*p == ' ') p++;
13886
13887     /* Active color */
13888     switch (*p++) {
13889       case 'w':
13890         *blackPlaysFirst = FALSE;
13891         break;
13892       case 'b': 
13893         *blackPlaysFirst = TRUE;
13894         break;
13895       default:
13896         return FALSE;
13897     }
13898
13899     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13900     /* return the extra info in global variiables             */
13901
13902     /* set defaults in case FEN is incomplete */
13903     FENepStatus = EP_UNKNOWN;
13904     for(i=0; i<nrCastlingRights; i++ ) {
13905         FENcastlingRights[i] =
13906             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13907     }   /* assume possible unless obviously impossible */
13908     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13909     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13910     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13911     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13912     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13913     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13914     FENrulePlies = 0;
13915
13916     while(*p==' ') p++;
13917     if(nrCastlingRights) {
13918       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13919           /* castling indicator present, so default becomes no castlings */
13920           for(i=0; i<nrCastlingRights; i++ ) {
13921                  FENcastlingRights[i] = -1;
13922           }
13923       }
13924       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13925              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13926              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13927              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13928         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13929
13930         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13931             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13932             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13933         }
13934         switch(c) {
13935           case'K':
13936               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13937               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13938               FENcastlingRights[2] = whiteKingFile;
13939               break;
13940           case'Q':
13941               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13942               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13943               FENcastlingRights[2] = whiteKingFile;
13944               break;
13945           case'k':
13946               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13947               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13948               FENcastlingRights[5] = blackKingFile;
13949               break;
13950           case'q':
13951               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13952               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13953               FENcastlingRights[5] = blackKingFile;
13954           case '-':
13955               break;
13956           default: /* FRC castlings */
13957               if(c >= 'a') { /* black rights */
13958                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13959                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13960                   if(i == BOARD_RGHT) break;
13961                   FENcastlingRights[5] = i;
13962                   c -= AAA;
13963                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13964                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13965                   if(c > i)
13966                       FENcastlingRights[3] = c;
13967                   else
13968                       FENcastlingRights[4] = c;
13969               } else { /* white rights */
13970                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13971                     if(board[0][i] == WhiteKing) break;
13972                   if(i == BOARD_RGHT) break;
13973                   FENcastlingRights[2] = i;
13974                   c -= AAA - 'a' + 'A';
13975                   if(board[0][c] >= WhiteKing) break;
13976                   if(c > i)
13977                       FENcastlingRights[0] = c;
13978                   else
13979                       FENcastlingRights[1] = c;
13980               }
13981         }
13982       }
13983     if (appData.debugMode) {
13984         fprintf(debugFP, "FEN castling rights:");
13985         for(i=0; i<nrCastlingRights; i++)
13986         fprintf(debugFP, " %d", FENcastlingRights[i]);
13987         fprintf(debugFP, "\n");
13988     }
13989
13990       while(*p==' ') p++;
13991     }
13992
13993     /* read e.p. field in games that know e.p. capture */
13994     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13995        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13996       if(*p=='-') {
13997         p++; FENepStatus = EP_NONE;
13998       } else {
13999          char c = *p++ - AAA;
14000
14001          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14002          if(*p >= '0' && *p <='9') *p++;
14003          FENepStatus = c;
14004       }
14005     }
14006
14007
14008     if(sscanf(p, "%d", &i) == 1) {
14009         FENrulePlies = i; /* 50-move ply counter */
14010         /* (The move number is still ignored)    */
14011     }
14012
14013     return TRUE;
14014 }
14015       
14016 void
14017 EditPositionPasteFEN(char *fen)
14018 {
14019   if (fen != NULL) {
14020     Board initial_position;
14021
14022     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14023       DisplayError(_("Bad FEN position in clipboard"), 0);
14024       return ;
14025     } else {
14026       int savedBlackPlaysFirst = blackPlaysFirst;
14027       EditPositionEvent();
14028       blackPlaysFirst = savedBlackPlaysFirst;
14029       CopyBoard(boards[0], initial_position);
14030           /* [HGM] copy FEN attributes as well */
14031           {   int i;
14032               initialRulePlies = FENrulePlies;
14033               epStatus[0] = FENepStatus;
14034               for( i=0; i<nrCastlingRights; i++ )
14035                   castlingRights[0][i] = FENcastlingRights[i];
14036           }
14037       EditPositionDone();
14038       DisplayBothClocks();
14039       DrawPosition(FALSE, boards[currentMove]);
14040     }
14041   }
14042 }
14043
14044 static char cseq[12] = "\\   ";
14045
14046 Boolean set_cont_sequence(char *new_seq)
14047 {
14048     int len;
14049     Boolean ret;
14050
14051     // handle bad attempts to set the sequence
14052         if (!new_seq)
14053                 return 0; // acceptable error - no debug
14054
14055     len = strlen(new_seq);
14056     ret = (len > 0) && (len < sizeof(cseq));
14057     if (ret)
14058         strcpy(cseq, new_seq);
14059     else if (appData.debugMode)
14060         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14061     return ret;
14062 }
14063
14064 /*
14065     reformat a source message so words don't cross the width boundary.  internal
14066     newlines are not removed.  returns the wrapped size (no null character unless
14067     included in source message).  If dest is NULL, only calculate the size required
14068     for the dest buffer.  lp argument indicats line position upon entry, and it's
14069     passed back upon exit.
14070 */
14071 int wrap(char *dest, char *src, int count, int width, int *lp)
14072 {
14073     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14074
14075     cseq_len = strlen(cseq);
14076     old_line = line = *lp;
14077     ansi = len = clen = 0;
14078
14079     for (i=0; i < count; i++)
14080     {
14081         if (src[i] == '\033')
14082             ansi = 1;
14083
14084         // if we hit the width, back up
14085         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14086         {
14087             // store i & len in case the word is too long
14088             old_i = i, old_len = len;
14089
14090             // find the end of the last word
14091             while (i && src[i] != ' ' && src[i] != '\n')
14092             {
14093                 i--;
14094                 len--;
14095             }
14096
14097             // word too long?  restore i & len before splitting it
14098             if ((old_i-i+clen) >= width)
14099             {
14100                 i = old_i;
14101                 len = old_len;
14102             }
14103
14104             // extra space?
14105             if (i && src[i-1] == ' ')
14106                 len--;
14107
14108             if (src[i] != ' ' && src[i] != '\n')
14109             {
14110                 i--;
14111                 if (len)
14112                     len--;
14113             }
14114
14115             // now append the newline and continuation sequence
14116             if (dest)
14117                 dest[len] = '\n';
14118             len++;
14119             if (dest)
14120                 strncpy(dest+len, cseq, cseq_len);
14121             len += cseq_len;
14122             line = cseq_len;
14123             clen = cseq_len;
14124             continue;
14125         }
14126
14127         if (dest)
14128             dest[len] = src[i];
14129         len++;
14130         if (!ansi)
14131             line++;
14132         if (src[i] == '\n')
14133             line = 0;
14134         if (src[i] == 'm')
14135             ansi = 0;
14136     }
14137     if (dest && appData.debugMode)
14138     {
14139         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14140             count, width, line, len, *lp);
14141         show_bytes(debugFP, src, count);
14142         fprintf(debugFP, "\ndest: ");
14143         show_bytes(debugFP, dest, len);
14144         fprintf(debugFP, "\n");
14145     }
14146     *lp = dest ? line : old_line;
14147
14148     return len;
14149 }