smarter analysis of the boards that XBoard receives from an ICS
[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); // [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         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801     
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811     
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000; 
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041     if (appData.debugMode) {
1042         fprintf(debugFP, "%s\n", programVersion);
1043     }
1044
1045     set_cont_sequence(appData.wrapContSeq);
1046     if (appData.matchGames > 0) {
1047         appData.matchMode = TRUE;
1048     } else if (appData.matchMode) {
1049         appData.matchGames = 1;
1050     }
1051     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052         appData.matchGames = appData.sameColorGames;
1053     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056     }
1057     Reset(TRUE, FALSE);
1058     if (appData.noChessProgram || first.protocolVersion == 1) {
1059       InitBackEnd3();
1060     } else {
1061       /* kludge: allow timeout for initial "feature" commands */
1062       FreezeUI();
1063       DisplayMessage("", _("Starting chess program"));
1064       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065     }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate(); 
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),  
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112     
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile; 
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway, 
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '(' 
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr") 
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in 
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754     
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        return;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2016    
2017    DrawPosition(TRUE, boards[currentMove]);
2018 }
2019
2020 static int loggedOn = FALSE;
2021
2022 /*-- Game start info cache: --*/
2023 int gs_gamenum;
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\   ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2031
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2034
2035 void
2036 read_from_ics(isr, closure, data, count, error)
2037      InputSourceRef isr;
2038      VOIDSTAR closure;
2039      char *data;
2040      int count;
2041      int error;
2042 {
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2052     
2053     static int started = STARTED_NONE;
2054     static char parse[20000];
2055     static int parse_pos = 0;
2056     static char buf[BUF_SIZE + 1];
2057     static int firstTime = TRUE, intfSet = FALSE;
2058     static ColorClass prevColor = ColorNormal;
2059     static int savingComment = FALSE;
2060     static int cmatch = 0; // continuation sequence match
2061     char *bp;
2062     char str[500];
2063     int i, oldi;
2064     int buf_len;
2065     int next_out;
2066     int tkind;
2067     int backup;    /* [DM] For zippy color lines */
2068     char *p;
2069     char talker[MSG_SIZ]; // [HGM] chat
2070     int channel;
2071
2072     if (appData.debugMode) {
2073       if (!error) {
2074         fprintf(debugFP, "<ICS: ");
2075         show_bytes(debugFP, data, count);
2076         fprintf(debugFP, "\n");
2077       }
2078     }
2079
2080     if (appData.debugMode) { int f = forwardMostMove;
2081         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2083     }
2084     if (count > 0) {
2085         /* If last read ended with a partial line that we couldn't parse,
2086            prepend it to the new read and try again. */
2087         if (leftover_len > 0) {
2088             for (i=0; i<leftover_len; i++)
2089               buf[i] = buf[leftover_start + i];
2090         }
2091
2092     /* copy new characters into the buffer */
2093     bp = buf + leftover_len;
2094     buf_len=leftover_len;
2095     for (i=0; i<count; i++)
2096     {
2097         // ignore these
2098         if (data[i] == '\r')
2099             continue;
2100
2101         // join lines split by ICS?
2102         if (!appData.noJoin)
2103         {
2104             /*
2105                 Joining just consists of finding matches against the
2106                 continuation sequence, and discarding that sequence
2107                 if found instead of copying it.  So, until a match
2108                 fails, there's nothing to do since it might be the
2109                 complete sequence, and thus, something we don't want
2110                 copied.
2111             */
2112             if (data[i] == cont_seq[cmatch])
2113             {
2114                 cmatch++;
2115                 if (cmatch == strlen(cont_seq))
2116                 {
2117                     cmatch = 0; // complete match.  just reset the counter
2118
2119                     /*
2120                         it's possible for the ICS to not include the space
2121                         at the end of the last word, making our [correct]
2122                         join operation fuse two separate words.  the server
2123                         does this when the space occurs at the width setting.
2124                     */
2125                     if (!buf_len || buf[buf_len-1] != ' ')
2126                     {
2127                         *bp++ = ' ';
2128                         buf_len++;
2129                     }
2130                 }
2131                 continue;
2132             }
2133             else if (cmatch)
2134             {
2135                 /*
2136                     match failed, so we have to copy what matched before
2137                     falling through and copying this character.  In reality,
2138                     this will only ever be just the newline character, but
2139                     it doesn't hurt to be precise.
2140                 */
2141                 strncpy(bp, cont_seq, cmatch);
2142                 bp += cmatch;
2143                 buf_len += cmatch;
2144                 cmatch = 0;
2145             }
2146         }
2147
2148         // copy this char
2149         *bp++ = data[i];
2150         buf_len++;
2151     }
2152
2153         buf[buf_len] = NULLCHAR;
2154         next_out = leftover_len;
2155         leftover_start = 0;
2156         
2157         i = 0;
2158         while (i < buf_len) {
2159             /* Deal with part of the TELNET option negotiation
2160                protocol.  We refuse to do anything beyond the
2161                defaults, except that we allow the WILL ECHO option,
2162                which ICS uses to turn off password echoing when we are
2163                directly connected to it.  We reject this option
2164                if localLineEditing mode is on (always on in xboard)
2165                and we are talking to port 23, which might be a real
2166                telnet server that will try to keep WILL ECHO on permanently.
2167              */
2168             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170                 unsigned char option;
2171                 oldi = i;
2172                 switch ((unsigned char) buf[++i]) {
2173                   case TN_WILL:
2174                     if (appData.debugMode)
2175                       fprintf(debugFP, "\n<WILL ");
2176                     switch (option = (unsigned char) buf[++i]) {
2177                       case TN_ECHO:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "ECHO ");
2180                         /* Reply only if this is a change, according
2181                            to the protocol rules. */
2182                         if (remoteEchoOption) break;
2183                         if (appData.localLineEditing &&
2184                             atoi(appData.icsPort) == TN_PORT) {
2185                             TelnetRequest(TN_DONT, TN_ECHO);
2186                         } else {
2187                             EchoOff();
2188                             TelnetRequest(TN_DO, TN_ECHO);
2189                             remoteEchoOption = TRUE;
2190                         }
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         /* Whatever this is, we don't want it. */
2196                         TelnetRequest(TN_DONT, option);
2197                         break;
2198                     }
2199                     break;
2200                   case TN_WONT:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<WONT ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       case TN_ECHO:
2205                         if (appData.debugMode)
2206                           fprintf(debugFP, "ECHO ");
2207                         /* Reply only if this is a change, according
2208                            to the protocol rules. */
2209                         if (!remoteEchoOption) break;
2210                         EchoOn();
2211                         TelnetRequest(TN_DONT, TN_ECHO);
2212                         remoteEchoOption = FALSE;
2213                         break;
2214                       default:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "%d ", (unsigned char) option);
2217                         /* Whatever this is, it must already be turned
2218                            off, because we never agree to turn on
2219                            anything non-default, so according to the
2220                            protocol rules, we don't reply. */
2221                         break;
2222                     }
2223                     break;
2224                   case TN_DO:
2225                     if (appData.debugMode)
2226                       fprintf(debugFP, "\n<DO ");
2227                     switch (option = (unsigned char) buf[++i]) {
2228                       default:
2229                         /* Whatever this is, we refuse to do it. */
2230                         if (appData.debugMode)
2231                           fprintf(debugFP, "%d ", option);
2232                         TelnetRequest(TN_WONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_DONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<DONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       default:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "%d ", option);
2243                         /* Whatever this is, we are already not doing
2244                            it, because we never agree to do anything
2245                            non-default, so according to the protocol
2246                            rules, we don't reply. */
2247                         break;
2248                     }
2249                     break;
2250                   case TN_IAC:
2251                     if (appData.debugMode)
2252                       fprintf(debugFP, "\n<IAC ");
2253                     /* Doubled IAC; pass it through */
2254                     i--;
2255                     break;
2256                   default:
2257                     if (appData.debugMode)
2258                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259                     /* Drop all other telnet commands on the floor */
2260                     break;
2261                 }
2262                 if (oldi > next_out)
2263                   SendToPlayer(&buf[next_out], oldi - next_out);
2264                 if (++i > next_out)
2265                   next_out = i;
2266                 continue;
2267             }
2268                 
2269             /* OK, this at least will *usually* work */
2270             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2271                 loggedOn = TRUE;
2272             }
2273             
2274             if (loggedOn && !intfSet) {
2275                 if (ics_type == ICS_ICC) {
2276                   sprintf(str,
2277                           "/set-quietly interface %s\n/set-quietly style 12\n",
2278                           programVersion);
2279                 } else if (ics_type == ICS_CHESSNET) {
2280                   sprintf(str, "/style 12\n");
2281                 } else {
2282                   strcpy(str, "alias $ @\n$set interface ");
2283                   strcat(str, programVersion);
2284                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2285 #ifdef WIN32
2286                   strcat(str, "$iset nohighlight 1\n");
2287 #endif
2288                   strcat(str, "$iset lock 1\n$style 12\n");
2289                 }
2290                 SendToICS(str);
2291                 NotifyFrontendLogin();
2292                 intfSet = TRUE;
2293             }
2294
2295             if (started == STARTED_COMMENT) {
2296                 /* Accumulate characters in comment */
2297                 parse[parse_pos++] = buf[i];
2298                 if (buf[i] == '\n') {
2299                     parse[parse_pos] = NULLCHAR;
2300                     if(chattingPartner>=0) {
2301                         char mess[MSG_SIZ];
2302                         sprintf(mess, "%s%s", talker, parse);
2303                         OutputChatMessage(chattingPartner, mess);
2304                         chattingPartner = -1;
2305                     } else
2306                     if(!suppressKibitz) // [HGM] kibitz
2307                         AppendComment(forwardMostMove, StripHighlight(parse));
2308                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309                         int nrDigit = 0, nrAlph = 0, i;
2310                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312                         parse[parse_pos] = NULLCHAR;
2313                         // try to be smart: if it does not look like search info, it should go to
2314                         // ICS interaction window after all, not to engine-output window.
2315                         for(i=0; i<parse_pos; i++) { // count letters and digits
2316                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2318                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2319                         }
2320                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321                             int depth=0; float score;
2322                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324                                 pvInfoList[forwardMostMove-1].depth = depth;
2325                                 pvInfoList[forwardMostMove-1].score = 100*score;
2326                             }
2327                             OutputKibitz(suppressKibitz, parse);
2328                         } else {
2329                             char tmp[MSG_SIZ];
2330                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331                             SendToPlayer(tmp, strlen(tmp));
2332                         }
2333                     }
2334                     started = STARTED_NONE;
2335                 } else {
2336                     /* Don't match patterns against characters in chatter */
2337                     i++;
2338                     continue;
2339                 }
2340             }
2341             if (started == STARTED_CHATTER) {
2342                 if (buf[i] != '\n') {
2343                     /* Don't match patterns against characters in chatter */
2344                     i++;
2345                     continue;
2346                 }
2347                 started = STARTED_NONE;
2348             }
2349
2350             /* Kludge to deal with rcmd protocol */
2351             if (firstTime && looking_at(buf, &i, "\001*")) {
2352                 DisplayFatalError(&buf[1], 0, 1);
2353                 continue;
2354             } else {
2355                 firstTime = FALSE;
2356             }
2357
2358             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2359                 ics_type = ICS_ICC;
2360                 ics_prefix = "/";
2361                 if (appData.debugMode)
2362                   fprintf(debugFP, "ics_type %d\n", ics_type);
2363                 continue;
2364             }
2365             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366                 ics_type = ICS_FICS;
2367                 ics_prefix = "$";
2368                 if (appData.debugMode)
2369                   fprintf(debugFP, "ics_type %d\n", ics_type);
2370                 continue;
2371             }
2372             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373                 ics_type = ICS_CHESSNET;
2374                 ics_prefix = "/";
2375                 if (appData.debugMode)
2376                   fprintf(debugFP, "ics_type %d\n", ics_type);
2377                 continue;
2378             }
2379
2380             if (!loggedOn &&
2381                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2383                  looking_at(buf, &i, "will be \"*\""))) {
2384               strcpy(ics_handle, star_match[0]);
2385               continue;
2386             }
2387
2388             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2389               char buf[MSG_SIZ];
2390               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391               DisplayIcsInteractionTitle(buf);
2392               have_set_title = TRUE;
2393             }
2394
2395             /* skip finger notes */
2396             if (started == STARTED_NONE &&
2397                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398                  (buf[i] == '1' && buf[i+1] == '0')) &&
2399                 buf[i+2] == ':' && buf[i+3] == ' ') {
2400               started = STARTED_CHATTER;
2401               i += 3;
2402               continue;
2403             }
2404
2405             /* skip formula vars */
2406             if (started == STARTED_NONE &&
2407                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408               started = STARTED_CHATTER;
2409               i += 3;
2410               continue;
2411             }
2412
2413             oldi = i;
2414             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415             if (appData.autoKibitz && started == STARTED_NONE && 
2416                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2417                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418                 if(looking_at(buf, &i, "* kibitzes: ") &&
2419                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2420                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2421                         suppressKibitz = TRUE;
2422                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423                                 && (gameMode == IcsPlayingWhite)) ||
2424                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2426                             started = STARTED_CHATTER; // own kibitz we simply discard
2427                         else {
2428                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429                             parse_pos = 0; parse[0] = NULLCHAR;
2430                             savingComment = TRUE;
2431                             suppressKibitz = gameMode != IcsObserving ? 2 :
2432                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2433                         } 
2434                         continue;
2435                 } else
2436                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437                     started = STARTED_CHATTER;
2438                     suppressKibitz = TRUE;
2439                 }
2440             } // [HGM] kibitz: end of patch
2441
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2443
2444             // [HGM] chat: intercept tells by users for which we have an open chat window
2445             channel = -1;
2446             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2447                                            looking_at(buf, &i, "* whispers:") ||
2448                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2450                 int p;
2451                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452                 chattingPartner = -1;
2453
2454                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455                 for(p=0; p<MAX_CHAT; p++) {
2456                     if(channel == atoi(chatPartner[p])) {
2457                     talker[0] = '['; strcat(talker, "]");
2458                     chattingPartner = p; break;
2459                     }
2460                 } else
2461                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462                 for(p=0; p<MAX_CHAT; p++) {
2463                     if(!strcmp("WHISPER", chatPartner[p])) {
2464                         talker[0] = '['; strcat(talker, "]");
2465                         chattingPartner = p; break;
2466                     }
2467                 }
2468                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2470                     talker[0] = 0;
2471                     chattingPartner = p; break;
2472                 }
2473                 if(chattingPartner<0) i = oldi; else {
2474                     started = STARTED_COMMENT;
2475                     parse_pos = 0; parse[0] = NULLCHAR;
2476                     savingComment = TRUE;
2477                     suppressKibitz = TRUE;
2478                 }
2479             } // [HGM] chat: end of patch
2480
2481             if (appData.zippyTalk || appData.zippyPlay) {
2482                 /* [DM] Backup address for color zippy lines */
2483                 backup = i;
2484 #if ZIPPY
2485        #ifdef WIN32
2486                if (loggedOn == TRUE)
2487                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2489        #else
2490                 if (ZippyControl(buf, &i) ||
2491                     ZippyConverse(buf, &i) ||
2492                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2493                       loggedOn = TRUE;
2494                       if (!appData.colorize) continue;
2495                 }
2496        #endif
2497 #endif
2498             } // [DM] 'else { ' deleted
2499                 if (
2500                     /* Regular tells and says */
2501                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2503                     looking_at(buf, &i, "* says: ") ||
2504                     /* Don't color "message" or "messages" output */
2505                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506                     looking_at(buf, &i, "*. * at *:*: ") ||
2507                     looking_at(buf, &i, "--* (*:*): ") ||
2508                     /* Message notifications (same color as tells) */
2509                     looking_at(buf, &i, "* has left a message ") ||
2510                     looking_at(buf, &i, "* just sent you a message:\n") ||
2511                     /* Whispers and kibitzes */
2512                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513                     looking_at(buf, &i, "* kibitzes: ") ||
2514                     /* Channel tells */
2515                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2516
2517                   if (tkind == 1 && strchr(star_match[0], ':')) {
2518                       /* Avoid "tells you:" spoofs in channels */
2519                      tkind = 3;
2520                   }
2521                   if (star_match[0][0] == NULLCHAR ||
2522                       strchr(star_match[0], ' ') ||
2523                       (tkind == 3 && strchr(star_match[1], ' '))) {
2524                     /* Reject bogus matches */
2525                     i = oldi;
2526                   } else {
2527                     if (appData.colorize) {
2528                       if (oldi > next_out) {
2529                         SendToPlayer(&buf[next_out], oldi - next_out);
2530                         next_out = oldi;
2531                       }
2532                       switch (tkind) {
2533                       case 1:
2534                         Colorize(ColorTell, FALSE);
2535                         curColor = ColorTell;
2536                         break;
2537                       case 2:
2538                         Colorize(ColorKibitz, FALSE);
2539                         curColor = ColorKibitz;
2540                         break;
2541                       case 3:
2542                         p = strrchr(star_match[1], '(');
2543                         if (p == NULL) {
2544                           p = star_match[1];
2545                         } else {
2546                           p++;
2547                         }
2548                         if (atoi(p) == 1) {
2549                           Colorize(ColorChannel1, FALSE);
2550                           curColor = ColorChannel1;
2551                         } else {
2552                           Colorize(ColorChannel, FALSE);
2553                           curColor = ColorChannel;
2554                         }
2555                         break;
2556                       case 5:
2557                         curColor = ColorNormal;
2558                         break;
2559                       }
2560                     }
2561                     if (started == STARTED_NONE && appData.autoComment &&
2562                         (gameMode == IcsObserving ||
2563                          gameMode == IcsPlayingWhite ||
2564                          gameMode == IcsPlayingBlack)) {
2565                       parse_pos = i - oldi;
2566                       memcpy(parse, &buf[oldi], parse_pos);
2567                       parse[parse_pos] = NULLCHAR;
2568                       started = STARTED_COMMENT;
2569                       savingComment = TRUE;
2570                     } else {
2571                       started = STARTED_CHATTER;
2572                       savingComment = FALSE;
2573                     }
2574                     loggedOn = TRUE;
2575                     continue;
2576                   }
2577                 }
2578
2579                 if (looking_at(buf, &i, "* s-shouts: ") ||
2580                     looking_at(buf, &i, "* c-shouts: ")) {
2581                     if (appData.colorize) {
2582                         if (oldi > next_out) {
2583                             SendToPlayer(&buf[next_out], oldi - next_out);
2584                             next_out = oldi;
2585                         }
2586                         Colorize(ColorSShout, FALSE);
2587                         curColor = ColorSShout;
2588                     }
2589                     loggedOn = TRUE;
2590                     started = STARTED_CHATTER;
2591                     continue;
2592                 }
2593
2594                 if (looking_at(buf, &i, "--->")) {
2595                     loggedOn = TRUE;
2596                     continue;
2597                 }
2598
2599                 if (looking_at(buf, &i, "* shouts: ") ||
2600                     looking_at(buf, &i, "--> ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorShout, FALSE);
2607                         curColor = ColorShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at( buf, &i, "Challenge:")) {
2615                     if (appData.colorize) {
2616                         if (oldi > next_out) {
2617                             SendToPlayer(&buf[next_out], oldi - next_out);
2618                             next_out = oldi;
2619                         }
2620                         Colorize(ColorChallenge, FALSE);
2621                         curColor = ColorChallenge;
2622                     }
2623                     loggedOn = TRUE;
2624                     continue;
2625                 }
2626
2627                 if (looking_at(buf, &i, "* offers you") ||
2628                     looking_at(buf, &i, "* offers to be") ||
2629                     looking_at(buf, &i, "* would like to") ||
2630                     looking_at(buf, &i, "* requests to") ||
2631                     looking_at(buf, &i, "Your opponent offers") ||
2632                     looking_at(buf, &i, "Your opponent requests")) {
2633
2634                     if (appData.colorize) {
2635                         if (oldi > next_out) {
2636                             SendToPlayer(&buf[next_out], oldi - next_out);
2637                             next_out = oldi;
2638                         }
2639                         Colorize(ColorRequest, FALSE);
2640                         curColor = ColorRequest;
2641                     }
2642                     continue;
2643                 }
2644
2645                 if (looking_at(buf, &i, "* (*) seeking")) {
2646                     if (appData.colorize) {
2647                         if (oldi > next_out) {
2648                             SendToPlayer(&buf[next_out], oldi - next_out);
2649                             next_out = oldi;
2650                         }
2651                         Colorize(ColorSeek, FALSE);
2652                         curColor = ColorSeek;
2653                     }
2654                     continue;
2655             }
2656
2657             if (looking_at(buf, &i, "\\   ")) {
2658                 if (prevColor != ColorNormal) {
2659                     if (oldi > next_out) {
2660                         SendToPlayer(&buf[next_out], oldi - next_out);
2661                         next_out = oldi;
2662                     }
2663                     Colorize(prevColor, TRUE);
2664                     curColor = prevColor;
2665                 }
2666                 if (savingComment) {
2667                     parse_pos = i - oldi;
2668                     memcpy(parse, &buf[oldi], parse_pos);
2669                     parse[parse_pos] = NULLCHAR;
2670                     started = STARTED_COMMENT;
2671                 } else {
2672                     started = STARTED_CHATTER;
2673                 }
2674                 continue;
2675             }
2676
2677             if (looking_at(buf, &i, "Black Strength :") ||
2678                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679                 looking_at(buf, &i, "<10>") ||
2680                 looking_at(buf, &i, "#@#")) {
2681                 /* Wrong board style */
2682                 loggedOn = TRUE;
2683                 SendToICS(ics_prefix);
2684                 SendToICS("set style 12\n");
2685                 SendToICS(ics_prefix);
2686                 SendToICS("refresh\n");
2687                 continue;
2688             }
2689             
2690             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2691                 ICSInitScript();
2692                 have_sent_ICS_logon = 1;
2693                 continue;
2694             }
2695               
2696             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2697                 (looking_at(buf, &i, "\n<12> ") ||
2698                  looking_at(buf, &i, "<12> "))) {
2699                 loggedOn = TRUE;
2700                 if (oldi > next_out) {
2701                     SendToPlayer(&buf[next_out], oldi - next_out);
2702                 }
2703                 next_out = i;
2704                 started = STARTED_BOARD;
2705                 parse_pos = 0;
2706                 continue;
2707             }
2708
2709             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710                 looking_at(buf, &i, "<b1> ")) {
2711                 if (oldi > next_out) {
2712                     SendToPlayer(&buf[next_out], oldi - next_out);
2713                 }
2714                 next_out = i;
2715                 started = STARTED_HOLDINGS;
2716                 parse_pos = 0;
2717                 continue;
2718             }
2719
2720             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2721                 loggedOn = TRUE;
2722                 /* Header for a move list -- first line */
2723
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     switch (gameMode) {
2727                       case IcsIdle:
2728                       case BeginningOfGame:
2729                         /* User typed "moves" or "oldmoves" while we
2730                            were idle.  Pretend we asked for these
2731                            moves and soak them up so user can step
2732                            through them and/or save them.
2733                            */
2734                         Reset(FALSE, TRUE);
2735                         gameMode = IcsObserving;
2736                         ModeHighlight();
2737                         ics_gamenum = -1;
2738                         ics_getting_history = H_GOT_UNREQ_HEADER;
2739                         break;
2740                       case EditGame: /*?*/
2741                       case EditPosition: /*?*/
2742                         /* Should above feature work in these modes too? */
2743                         /* For now it doesn't */
2744                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2745                         break;
2746                       default:
2747                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2748                         break;
2749                     }
2750                     break;
2751                   case H_REQUESTED:
2752                     /* Is this the right one? */
2753                     if (gameInfo.white && gameInfo.black &&
2754                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2755                         strcmp(gameInfo.black, star_match[2]) == 0) {
2756                         /* All is well */
2757                         ics_getting_history = H_GOT_REQ_HEADER;
2758                     }
2759                     break;
2760                   case H_GOT_REQ_HEADER:
2761                   case H_GOT_UNREQ_HEADER:
2762                   case H_GOT_UNWANTED_HEADER:
2763                   case H_GETTING_MOVES:
2764                     /* Should not happen */
2765                     DisplayError(_("Error gathering move list: two headers"), 0);
2766                     ics_getting_history = H_FALSE;
2767                     break;
2768                 }
2769
2770                 /* Save player ratings into gameInfo if needed */
2771                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773                     (gameInfo.whiteRating == -1 ||
2774                      gameInfo.blackRating == -1)) {
2775
2776                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2777                     gameInfo.blackRating = string_to_rating(star_match[3]);
2778                     if (appData.debugMode)
2779                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2780                               gameInfo.whiteRating, gameInfo.blackRating);
2781                 }
2782                 continue;
2783             }
2784
2785             if (looking_at(buf, &i,
2786               "* * match, initial time: * minute*, increment: * second")) {
2787                 /* Header for a move list -- second line */
2788                 /* Initial board will follow if this is a wild game */
2789                 if (gameInfo.event != NULL) free(gameInfo.event);
2790                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791                 gameInfo.event = StrSave(str);
2792                 /* [HGM] we switched variant. Translate boards if needed. */
2793                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2794                 continue;
2795             }
2796
2797             if (looking_at(buf, &i, "Move  ")) {
2798                 /* Beginning of a move list */
2799                 switch (ics_getting_history) {
2800                   case H_FALSE:
2801                     /* Normally should not happen */
2802                     /* Maybe user hit reset while we were parsing */
2803                     break;
2804                   case H_REQUESTED:
2805                     /* Happens if we are ignoring a move list that is not
2806                      * the one we just requested.  Common if the user
2807                      * tries to observe two games without turning off
2808                      * getMoveList */
2809                     break;
2810                   case H_GETTING_MOVES:
2811                     /* Should not happen */
2812                     DisplayError(_("Error gathering move list: nested"), 0);
2813                     ics_getting_history = H_FALSE;
2814                     break;
2815                   case H_GOT_REQ_HEADER:
2816                     ics_getting_history = H_GETTING_MOVES;
2817                     started = STARTED_MOVES;
2818                     parse_pos = 0;
2819                     if (oldi > next_out) {
2820                         SendToPlayer(&buf[next_out], oldi - next_out);
2821                     }
2822                     break;
2823                   case H_GOT_UNREQ_HEADER:
2824                     ics_getting_history = H_GETTING_MOVES;
2825                     started = STARTED_MOVES_NOHIDE;
2826                     parse_pos = 0;
2827                     break;
2828                   case H_GOT_UNWANTED_HEADER:
2829                     ics_getting_history = H_FALSE;
2830                     break;
2831                 }
2832                 continue;
2833             }                           
2834             
2835             if (looking_at(buf, &i, "% ") ||
2836                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838                 savingComment = FALSE;
2839                 switch (started) {
2840                   case STARTED_MOVES:
2841                   case STARTED_MOVES_NOHIDE:
2842                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843                     parse[parse_pos + i - oldi] = NULLCHAR;
2844                     ParseGameHistory(parse);
2845 #if ZIPPY
2846                     if (appData.zippyPlay && first.initDone) {
2847                         FeedMovesToProgram(&first, forwardMostMove);
2848                         if (gameMode == IcsPlayingWhite) {
2849                             if (WhiteOnMove(forwardMostMove)) {
2850                                 if (first.sendTime) {
2851                                   if (first.useColors) {
2852                                     SendToProgram("black\n", &first); 
2853                                   }
2854                                   SendTimeRemaining(&first, TRUE);
2855                                 }
2856                                 if (first.useColors) {
2857                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2858                                 }
2859                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860                                 first.maybeThinking = TRUE;
2861                             } else {
2862                                 if (first.usePlayother) {
2863                                   if (first.sendTime) {
2864                                     SendTimeRemaining(&first, TRUE);
2865                                   }
2866                                   SendToProgram("playother\n", &first);
2867                                   firstMove = FALSE;
2868                                 } else {
2869                                   firstMove = TRUE;
2870                                 }
2871                             }
2872                         } else if (gameMode == IcsPlayingBlack) {
2873                             if (!WhiteOnMove(forwardMostMove)) {
2874                                 if (first.sendTime) {
2875                                   if (first.useColors) {
2876                                     SendToProgram("white\n", &first);
2877                                   }
2878                                   SendTimeRemaining(&first, FALSE);
2879                                 }
2880                                 if (first.useColors) {
2881                                   SendToProgram("black\n", &first);
2882                                 }
2883                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884                                 first.maybeThinking = TRUE;
2885                             } else {
2886                                 if (first.usePlayother) {
2887                                   if (first.sendTime) {
2888                                     SendTimeRemaining(&first, FALSE);
2889                                   }
2890                                   SendToProgram("playother\n", &first);
2891                                   firstMove = FALSE;
2892                                 } else {
2893                                   firstMove = TRUE;
2894                                 }
2895                             }
2896                         }                       
2897                     }
2898 #endif
2899                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2900                         /* Moves came from oldmoves or moves command
2901                            while we weren't doing anything else.
2902                            */
2903                         currentMove = forwardMostMove;
2904                         ClearHighlights();/*!!could figure this out*/
2905                         flipView = appData.flipView;
2906                         DrawPosition(FALSE, boards[currentMove]);
2907                         DisplayBothClocks();
2908                         sprintf(str, "%s vs. %s",
2909                                 gameInfo.white, gameInfo.black);
2910                         DisplayTitle(str);
2911                         gameMode = IcsIdle;
2912                     } else {
2913                         /* Moves were history of an active game */
2914                         if (gameInfo.resultDetails != NULL) {
2915                             free(gameInfo.resultDetails);
2916                             gameInfo.resultDetails = NULL;
2917                         }
2918                     }
2919                     HistorySet(parseList, backwardMostMove,
2920                                forwardMostMove, currentMove-1);
2921                     DisplayMove(currentMove - 1);
2922                     if (started == STARTED_MOVES) next_out = i;
2923                     started = STARTED_NONE;
2924                     ics_getting_history = H_FALSE;
2925                     break;
2926
2927                   case STARTED_OBSERVE:
2928                     started = STARTED_NONE;
2929                     SendToICS(ics_prefix);
2930                     SendToICS("refresh\n");
2931                     break;
2932
2933                   default:
2934                     break;
2935                 }
2936                 if(bookHit) { // [HGM] book: simulate book reply
2937                     static char bookMove[MSG_SIZ]; // a bit generous?
2938
2939                     programStats.nodes = programStats.depth = programStats.time = 
2940                     programStats.score = programStats.got_only_move = 0;
2941                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2942
2943                     strcpy(bookMove, "move ");
2944                     strcat(bookMove, bookHit);
2945                     HandleMachineMove(bookMove, &first);
2946                 }
2947                 continue;
2948             }
2949             
2950             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951                  started == STARTED_HOLDINGS ||
2952                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953                 /* Accumulate characters in move list or board */
2954                 parse[parse_pos++] = buf[i];
2955             }
2956             
2957             /* Start of game messages.  Mostly we detect start of game
2958                when the first board image arrives.  On some versions
2959                of the ICS, though, we need to do a "refresh" after starting
2960                to observe in order to get the current board right away. */
2961             if (looking_at(buf, &i, "Adding game * to observation list")) {
2962                 started = STARTED_OBSERVE;
2963                 continue;
2964             }
2965
2966             /* Handle auto-observe */
2967             if (appData.autoObserve &&
2968                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2970                 char *player;
2971                 /* Choose the player that was highlighted, if any. */
2972                 if (star_match[0][0] == '\033' ||
2973                     star_match[1][0] != '\033') {
2974                     player = star_match[0];
2975                 } else {
2976                     player = star_match[2];
2977                 }
2978                 sprintf(str, "%sobserve %s\n",
2979                         ics_prefix, StripHighlightAndTitle(player));
2980                 SendToICS(str);
2981
2982                 /* Save ratings from notify string */
2983                 strcpy(player1Name, star_match[0]);
2984                 player1Rating = string_to_rating(star_match[1]);
2985                 strcpy(player2Name, star_match[2]);
2986                 player2Rating = string_to_rating(star_match[3]);
2987
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, 
2990                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2991                           player1Name, player1Rating,
2992                           player2Name, player2Rating);
2993
2994                 continue;
2995             }
2996
2997             /* Deal with automatic examine mode after a game,
2998                and with IcsObserving -> IcsExamining transition */
2999             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000                 looking_at(buf, &i, "has made you an examiner of game *")) {
3001
3002                 int gamenum = atoi(star_match[0]);
3003                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004                     gamenum == ics_gamenum) {
3005                     /* We were already playing or observing this game;
3006                        no need to refetch history */
3007                     gameMode = IcsExamining;
3008                     if (pausing) {
3009                         pauseExamForwardMostMove = forwardMostMove;
3010                     } else if (currentMove < forwardMostMove) {
3011                         ForwardInner(forwardMostMove);
3012                     }
3013                 } else {
3014                     /* I don't think this case really can happen */
3015                     SendToICS(ics_prefix);
3016                     SendToICS("refresh\n");
3017                 }
3018                 continue;
3019             }    
3020             
3021             /* Error messages */
3022 //          if (ics_user_moved) {
3023             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024                 if (looking_at(buf, &i, "Illegal move") ||
3025                     looking_at(buf, &i, "Not a legal move") ||
3026                     looking_at(buf, &i, "Your king is in check") ||
3027                     looking_at(buf, &i, "It isn't your turn") ||
3028                     looking_at(buf, &i, "It is not your move")) {
3029                     /* Illegal move */
3030                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031                         currentMove = --forwardMostMove;
3032                         DisplayMove(currentMove - 1); /* before DMError */
3033                         DrawPosition(FALSE, boards[currentMove]);
3034                         SwitchClocks();
3035                         DisplayBothClocks();
3036                     }
3037                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038                     ics_user_moved = 0;
3039                     continue;
3040                 }
3041             }
3042
3043             if (looking_at(buf, &i, "still have time") ||
3044                 looking_at(buf, &i, "not out of time") ||
3045                 looking_at(buf, &i, "either player is out of time") ||
3046                 looking_at(buf, &i, "has timeseal; checking")) {
3047                 /* We must have called his flag a little too soon */
3048                 whiteFlag = blackFlag = FALSE;
3049                 continue;
3050             }
3051
3052             if (looking_at(buf, &i, "added * seconds to") ||
3053                 looking_at(buf, &i, "seconds were added to")) {
3054                 /* Update the clocks */
3055                 SendToICS(ics_prefix);
3056                 SendToICS("refresh\n");
3057                 continue;
3058             }
3059
3060             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061                 ics_clock_paused = TRUE;
3062                 StopClocks();
3063                 continue;
3064             }
3065
3066             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067                 ics_clock_paused = FALSE;
3068                 StartClocks();
3069                 continue;
3070             }
3071
3072             /* Grab player ratings from the Creating: message.
3073                Note we have to check for the special case when
3074                the ICS inserts things like [white] or [black]. */
3075             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3077                 /* star_matches:
3078                    0    player 1 name (not necessarily white)
3079                    1    player 1 rating
3080                    2    empty, white, or black (IGNORED)
3081                    3    player 2 name (not necessarily black)
3082                    4    player 2 rating
3083                    
3084                    The names/ratings are sorted out when the game
3085                    actually starts (below).
3086                 */
3087                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088                 player1Rating = string_to_rating(star_match[1]);
3089                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090                 player2Rating = string_to_rating(star_match[4]);
3091
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, 
3094                           "Ratings from 'Creating:' %s %d, %s %d\n",
3095                           player1Name, player1Rating,
3096                           player2Name, player2Rating);
3097
3098                 continue;
3099             }
3100             
3101             /* Improved generic start/end-of-game messages */
3102             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104                 /* If tkind == 0: */
3105                 /* star_match[0] is the game number */
3106                 /*           [1] is the white player's name */
3107                 /*           [2] is the black player's name */
3108                 /* For end-of-game: */
3109                 /*           [3] is the reason for the game end */
3110                 /*           [4] is a PGN end game-token, preceded by " " */
3111                 /* For start-of-game: */
3112                 /*           [3] begins with "Creating" or "Continuing" */
3113                 /*           [4] is " *" or empty (don't care). */
3114                 int gamenum = atoi(star_match[0]);
3115                 char *whitename, *blackname, *why, *endtoken;
3116                 ChessMove endtype = (ChessMove) 0;
3117
3118                 if (tkind == 0) {
3119                   whitename = star_match[1];
3120                   blackname = star_match[2];
3121                   why = star_match[3];
3122                   endtoken = star_match[4];
3123                 } else {
3124                   whitename = star_match[1];
3125                   blackname = star_match[3];
3126                   why = star_match[5];
3127                   endtoken = star_match[6];
3128                 }
3129
3130                 /* Game start messages */
3131                 if (strncmp(why, "Creating ", 9) == 0 ||
3132                     strncmp(why, "Continuing ", 11) == 0) {
3133                     gs_gamenum = gamenum;
3134                     strcpy(gs_kind, strchr(why, ' ') + 1);
3135 #if ZIPPY
3136                     if (appData.zippyPlay) {
3137                         ZippyGameStart(whitename, blackname);
3138                     }
3139 #endif /*ZIPPY*/
3140                     continue;
3141                 }
3142
3143                 /* Game end messages */
3144                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145                     ics_gamenum != gamenum) {
3146                     continue;
3147                 }
3148                 while (endtoken[0] == ' ') endtoken++;
3149                 switch (endtoken[0]) {
3150                   case '*':
3151                   default:
3152                     endtype = GameUnfinished;
3153                     break;
3154                   case '0':
3155                     endtype = BlackWins;
3156                     break;
3157                   case '1':
3158                     if (endtoken[1] == '/')
3159                       endtype = GameIsDrawn;
3160                     else
3161                       endtype = WhiteWins;
3162                     break;
3163                 }
3164                 GameEnds(endtype, why, GE_ICS);
3165 #if ZIPPY
3166                 if (appData.zippyPlay && first.initDone) {
3167                     ZippyGameEnd(endtype, why);
3168                     if (first.pr == NULL) {
3169                       /* Start the next process early so that we'll
3170                          be ready for the next challenge */
3171                       StartChessProgram(&first);
3172                     }
3173                     /* Send "new" early, in case this command takes
3174                        a long time to finish, so that we'll be ready
3175                        for the next challenge. */
3176                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177                     Reset(TRUE, TRUE);
3178                 }
3179 #endif /*ZIPPY*/
3180                 continue;
3181             }
3182
3183             if (looking_at(buf, &i, "Removing game * from observation") ||
3184                 looking_at(buf, &i, "no longer observing game *") ||
3185                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186                 if (gameMode == IcsObserving &&
3187                     atoi(star_match[0]) == ics_gamenum)
3188                   {
3189                       /* icsEngineAnalyze */
3190                       if (appData.icsEngineAnalyze) {
3191                             ExitAnalyzeMode();
3192                             ModeHighlight();
3193                       }
3194                       StopClocks();
3195                       gameMode = IcsIdle;
3196                       ics_gamenum = -1;
3197                       ics_user_moved = FALSE;
3198                   }
3199                 continue;
3200             }
3201
3202             if (looking_at(buf, &i, "no longer examining game *")) {
3203                 if (gameMode == IcsExamining &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             /* Advance leftover_start past any newlines we find,
3214                so only partial lines can get reparsed */
3215             if (looking_at(buf, &i, "\n")) {
3216                 prevColor = curColor;
3217                 if (curColor != ColorNormal) {
3218                     if (oldi > next_out) {
3219                         SendToPlayer(&buf[next_out], oldi - next_out);
3220                         next_out = oldi;
3221                     }
3222                     Colorize(ColorNormal, FALSE);
3223                     curColor = ColorNormal;
3224                 }
3225                 if (started == STARTED_BOARD) {
3226                     started = STARTED_NONE;
3227                     parse[parse_pos] = NULLCHAR;
3228                     ParseBoard12(parse);
3229                     ics_user_moved = 0;
3230
3231                     /* Send premove here */
3232                     if (appData.premove) {
3233                       char str[MSG_SIZ];
3234                       if (currentMove == 0 &&
3235                           gameMode == IcsPlayingWhite &&
3236                           appData.premoveWhite) {
3237                         sprintf(str, "%s%s\n", ics_prefix,
3238                                 appData.premoveWhiteText);
3239                         if (appData.debugMode)
3240                           fprintf(debugFP, "Sending premove:\n");
3241                         SendToICS(str);
3242                       } else if (currentMove == 1 &&
3243                                  gameMode == IcsPlayingBlack &&
3244                                  appData.premoveBlack) {
3245                         sprintf(str, "%s%s\n", ics_prefix,
3246                                 appData.premoveBlackText);
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                         SendToICS(str);
3250                       } else if (gotPremove) {
3251                         gotPremove = 0;
3252                         ClearPremoveHighlights();
3253                         if (appData.debugMode)
3254                           fprintf(debugFP, "Sending premove:\n");
3255                           UserMoveEvent(premoveFromX, premoveFromY, 
3256                                         premoveToX, premoveToY, 
3257                                         premovePromoChar);
3258                       }
3259                     }
3260
3261                     /* Usually suppress following prompt */
3262                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263                         if (looking_at(buf, &i, "*% ")) {
3264                             savingComment = FALSE;
3265                         }
3266                     }
3267                     next_out = i;
3268                 } else if (started == STARTED_HOLDINGS) {
3269                     int gamenum;
3270                     char new_piece[MSG_SIZ];
3271                     started = STARTED_NONE;
3272                     parse[parse_pos] = NULLCHAR;
3273                     if (appData.debugMode)
3274                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275                                                         parse, currentMove);
3276                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277                         gamenum == ics_gamenum) {
3278                         if (gameInfo.variant == VariantNormal) {
3279                           /* [HGM] We seem to switch variant during a game!
3280                            * Presumably no holdings were displayed, so we have
3281                            * to move the position two files to the right to
3282                            * create room for them!
3283                            */
3284                           VariantClass newVariant;
3285                           switch(gameInfo.boardWidth) { // base guess on board width
3286                                 case 9:  newVariant = VariantShogi; break;
3287                                 case 10: newVariant = VariantGreat; break;
3288                                 default: newVariant = VariantCrazyhouse; break;
3289                           }
3290                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3291                           /* Get a move list just to see the header, which
3292                              will tell us whether this is really bug or zh */
3293                           if (ics_getting_history == H_FALSE) {
3294                             ics_getting_history = H_REQUESTED;
3295                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3296                             SendToICS(str);
3297                           }
3298                         }
3299                         new_piece[0] = NULLCHAR;
3300                         sscanf(parse, "game %d white [%s black [%s <- %s",
3301                                &gamenum, white_holding, black_holding,
3302                                new_piece);
3303                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3304                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3305                         /* [HGM] copy holdings to board holdings area */
3306                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3307                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3308 #if ZIPPY
3309                         if (appData.zippyPlay && first.initDone) {
3310                             ZippyHoldings(white_holding, black_holding,
3311                                           new_piece);
3312                         }
3313 #endif /*ZIPPY*/
3314                         if (tinyLayout || smallLayout) {
3315                             char wh[16], bh[16];
3316                             PackHolding(wh, white_holding);
3317                             PackHolding(bh, black_holding);
3318                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3319                                     gameInfo.white, gameInfo.black);
3320                         } else {
3321                             sprintf(str, "%s [%s] vs. %s [%s]",
3322                                     gameInfo.white, white_holding,
3323                                     gameInfo.black, black_holding);
3324                         }
3325
3326                         DrawPosition(FALSE, boards[currentMove]);
3327                         DisplayTitle(str);
3328                     }
3329                     /* Suppress following prompt */
3330                     if (looking_at(buf, &i, "*% ")) {
3331                         savingComment = FALSE;
3332                     }
3333                     next_out = i;
3334                 }
3335                 continue;
3336             }
3337
3338             i++;                /* skip unparsed character and loop back */
3339         }
3340         
3341         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3342             started != STARTED_HOLDINGS && i > next_out) {
3343             SendToPlayer(&buf[next_out], i - next_out);
3344             next_out = i;
3345         }
3346         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3347         
3348         leftover_len = buf_len - leftover_start;
3349         /* if buffer ends with something we couldn't parse,
3350            reparse it after appending the next read */
3351         
3352     } else if (count == 0) {
3353         RemoveInputSource(isr);
3354         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3355     } else {
3356         DisplayFatalError(_("Error reading from ICS"), error, 1);
3357     }
3358 }
3359
3360
3361 /* Board style 12 looks like this:
3362    
3363    <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
3364    
3365  * The "<12> " is stripped before it gets to this routine.  The two
3366  * trailing 0's (flip state and clock ticking) are later addition, and
3367  * some chess servers may not have them, or may have only the first.
3368  * Additional trailing fields may be added in the future.  
3369  */
3370
3371 #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"
3372
3373 #define RELATION_OBSERVING_PLAYED    0
3374 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3375 #define RELATION_PLAYING_MYMOVE      1
3376 #define RELATION_PLAYING_NOTMYMOVE  -1
3377 #define RELATION_EXAMINING           2
3378 #define RELATION_ISOLATED_BOARD     -3
3379 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3380
3381 void
3382 ParseBoard12(string)
3383      char *string;
3384
3385     GameMode newGameMode;
3386     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3387     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3388     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3389     char to_play, board_chars[200];
3390     char move_str[500], str[500], elapsed_time[500];
3391     char black[32], white[32];
3392     Board board;
3393     int prevMove = currentMove;
3394     int ticking = 2;
3395     ChessMove moveType;
3396     int fromX, fromY, toX, toY;
3397     char promoChar;
3398     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3399     char *bookHit = NULL; // [HGM] book
3400     Boolean weird = FALSE;
3401
3402     fromX = fromY = toX = toY = -1;
3403     
3404     newGame = FALSE;
3405
3406     if (appData.debugMode)
3407       fprintf(debugFP, _("Parsing board: %s\n"), string);
3408
3409     move_str[0] = NULLCHAR;
3410     elapsed_time[0] = NULLCHAR;
3411     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3412         int  i = 0, j;
3413         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3414             if(string[i] == ' ') { ranks++; files = 0; }
3415             else files++;
3416             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3417             i++;
3418         }
3419         for(j = 0; j <i; j++) board_chars[j] = string[j];
3420         board_chars[i] = '\0';
3421         string += i + 1;
3422     }
3423     n = sscanf(string, PATTERN, &to_play, &double_push,
3424                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3425                &gamenum, white, black, &relation, &basetime, &increment,
3426                &white_stren, &black_stren, &white_time, &black_time,
3427                &moveNum, str, elapsed_time, move_str, &ics_flip,
3428                &ticking);
3429 fprintf(debugFP, "old: %dx%d   new: %dx%d weird=%d variant=%d\n",gameInfo.boardHeight,gameInfo.boardWidth,ranks,files,weird,gameInfo.variant);fflush(debugFP);
3430    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3431                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3432      /* [HGM] We seem to switch variant during a game!
3433       * Try to guess new variant from board size
3434       */
3435           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3436           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3437           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3438           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3439           if(ranks == 9 && files == 9)  newVariant = VariantShogi;
3440           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3441           /* Get a move list just to see the header, which
3442              will tell us whether this is really bug or zh */
3443           if (ics_getting_history == H_FALSE) {
3444             ics_getting_history = H_REQUESTED;
3445             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3446             SendToICS(str);
3447           }
3448     }
3449
3450     if (n < 21) {
3451         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3452         DisplayError(str, 0);
3453         return;
3454     }
3455
3456     /* Convert the move number to internal form */
3457     moveNum = (moveNum - 1) * 2;
3458     if (to_play == 'B') moveNum++;
3459     if (moveNum >= MAX_MOVES) {
3460       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3461                         0, 1);
3462       return;
3463     }
3464     
3465     switch (relation) {
3466       case RELATION_OBSERVING_PLAYED:
3467       case RELATION_OBSERVING_STATIC:
3468         if (gamenum == -1) {
3469             /* Old ICC buglet */
3470             relation = RELATION_OBSERVING_STATIC;
3471         }
3472         newGameMode = IcsObserving;
3473         break;
3474       case RELATION_PLAYING_MYMOVE:
3475       case RELATION_PLAYING_NOTMYMOVE:
3476         newGameMode =
3477           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3478             IcsPlayingWhite : IcsPlayingBlack;
3479         break;
3480       case RELATION_EXAMINING:
3481         newGameMode = IcsExamining;
3482         break;
3483       case RELATION_ISOLATED_BOARD:
3484       default:
3485         /* Just display this board.  If user was doing something else,
3486            we will forget about it until the next board comes. */ 
3487         newGameMode = IcsIdle;
3488         break;
3489       case RELATION_STARTING_POSITION:
3490         newGameMode = gameMode;
3491         break;
3492     }
3493     
3494     /* Modify behavior for initial board display on move listing
3495        of wild games.
3496        */
3497     switch (ics_getting_history) {
3498       case H_FALSE:
3499       case H_REQUESTED:
3500         break;
3501       case H_GOT_REQ_HEADER:
3502       case H_GOT_UNREQ_HEADER:
3503         /* This is the initial position of the current game */
3504         gamenum = ics_gamenum;
3505         moveNum = 0;            /* old ICS bug workaround */
3506         if (to_play == 'B') {
3507           startedFromSetupPosition = TRUE;
3508           blackPlaysFirst = TRUE;
3509           moveNum = 1;
3510           if (forwardMostMove == 0) forwardMostMove = 1;
3511           if (backwardMostMove == 0) backwardMostMove = 1;
3512           if (currentMove == 0) currentMove = 1;
3513         }
3514         newGameMode = gameMode;
3515         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3516         break;
3517       case H_GOT_UNWANTED_HEADER:
3518         /* This is an initial board that we don't want */
3519         return;
3520       case H_GETTING_MOVES:
3521         /* Should not happen */
3522         DisplayError(_("Error gathering move list: extra board"), 0);
3523         ics_getting_history = H_FALSE;
3524         return;
3525     }
3526     
3527     /* Take action if this is the first board of a new game, or of a
3528        different game than is currently being displayed.  */
3529     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3530         relation == RELATION_ISOLATED_BOARD) {
3531         
3532         /* Forget the old game and get the history (if any) of the new one */
3533         if (gameMode != BeginningOfGame) {
3534           Reset(FALSE, TRUE);
3535         }
3536         newGame = TRUE;
3537         if (appData.autoRaiseBoard) BoardToTop();
3538         prevMove = -3;
3539         if (gamenum == -1) {
3540             newGameMode = IcsIdle;
3541         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3542                    appData.getMoveList) {
3543             /* Need to get game history */
3544             ics_getting_history = H_REQUESTED;
3545             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3546             SendToICS(str);
3547         }
3548         
3549         /* Initially flip the board to have black on the bottom if playing
3550            black or if the ICS flip flag is set, but let the user change
3551            it with the Flip View button. */
3552         flipView = appData.autoFlipView ? 
3553           (newGameMode == IcsPlayingBlack) || ics_flip :
3554           appData.flipView;
3555         
3556         /* Done with values from previous mode; copy in new ones */
3557         gameMode = newGameMode;
3558         ModeHighlight();
3559         ics_gamenum = gamenum;
3560         if (gamenum == gs_gamenum) {
3561             int klen = strlen(gs_kind);
3562             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3563             sprintf(str, "ICS %s", gs_kind);
3564             gameInfo.event = StrSave(str);
3565         } else {
3566             gameInfo.event = StrSave("ICS game");
3567         }
3568         gameInfo.site = StrSave(appData.icsHost);
3569         gameInfo.date = PGNDate();
3570         gameInfo.round = StrSave("-");
3571         gameInfo.white = StrSave(white);
3572         gameInfo.black = StrSave(black);
3573         timeControl = basetime * 60 * 1000;
3574         timeControl_2 = 0;
3575         timeIncrement = increment * 1000;
3576         movesPerSession = 0;
3577         gameInfo.timeControl = TimeControlTagValue();
3578         VariantSwitch(board, StringToVariant(gameInfo.event) );
3579   if (appData.debugMode) {
3580     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3581     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3582     setbuf(debugFP, NULL);
3583   }
3584
3585         gameInfo.outOfBook = NULL;
3586         
3587         /* Do we have the ratings? */
3588         if (strcmp(player1Name, white) == 0 &&
3589             strcmp(player2Name, black) == 0) {
3590             if (appData.debugMode)
3591               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3592                       player1Rating, player2Rating);
3593             gameInfo.whiteRating = player1Rating;
3594             gameInfo.blackRating = player2Rating;
3595         } else if (strcmp(player2Name, white) == 0 &&
3596                    strcmp(player1Name, black) == 0) {
3597             if (appData.debugMode)
3598               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3599                       player2Rating, player1Rating);
3600             gameInfo.whiteRating = player2Rating;
3601             gameInfo.blackRating = player1Rating;
3602         }
3603         player1Name[0] = player2Name[0] = NULLCHAR;
3604
3605         /* Silence shouts if requested */
3606         if (appData.quietPlay &&
3607             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3608             SendToICS(ics_prefix);
3609             SendToICS("set shout 0\n");
3610         }
3611     }
3612     
3613     /* Deal with midgame name changes */
3614     if (!newGame) {
3615         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3616             if (gameInfo.white) free(gameInfo.white);
3617             gameInfo.white = StrSave(white);
3618         }
3619         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3620             if (gameInfo.black) free(gameInfo.black);
3621             gameInfo.black = StrSave(black);
3622         }
3623     }
3624     
3625     /* Throw away game result if anything actually changes in examine mode */
3626     if (gameMode == IcsExamining && !newGame) {
3627         gameInfo.result = GameUnfinished;
3628         if (gameInfo.resultDetails != NULL) {
3629             free(gameInfo.resultDetails);
3630             gameInfo.resultDetails = NULL;
3631         }
3632     }
3633     
3634     /* In pausing && IcsExamining mode, we ignore boards coming
3635        in if they are in a different variation than we are. */
3636     if (pauseExamInvalid) return;
3637     if (pausing && gameMode == IcsExamining) {
3638         if (moveNum <= pauseExamForwardMostMove) {
3639             pauseExamInvalid = TRUE;
3640             forwardMostMove = pauseExamForwardMostMove;
3641             return;
3642         }
3643     }
3644     
3645   if (appData.debugMode) {
3646     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3647   }
3648     /* Parse the board */
3649     for (k = 0; k < ranks; k++) {
3650       for (j = 0; j < files; j++)
3651         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3652       if(gameInfo.holdingsWidth > 1) {
3653            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3654            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3655       }
3656     }
3657     CopyBoard(boards[moveNum], board);
3658     if (moveNum == 0) {
3659         startedFromSetupPosition =
3660           !CompareBoards(board, initialPosition);
3661         if(startedFromSetupPosition)
3662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3663     }
3664
3665     /* [HGM] Set castling rights. Take the outermost Rooks,
3666        to make it also work for FRC opening positions. Note that board12
3667        is really defective for later FRC positions, as it has no way to
3668        indicate which Rook can castle if they are on the same side of King.
3669        For the initial position we grant rights to the outermost Rooks,
3670        and remember thos rights, and we then copy them on positions
3671        later in an FRC game. This means WB might not recognize castlings with
3672        Rooks that have moved back to their original position as illegal,
3673        but in ICS mode that is not its job anyway.
3674     */
3675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3677
3678         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3679             if(board[0][i] == WhiteRook) j = i;
3680         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3681         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3682             if(board[0][i] == WhiteRook) j = i;
3683         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3684         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3686         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3689         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3690
3691         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3692         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3693             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3694         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3695             if(board[BOARD_HEIGHT-1][k] == bKing)
3696                 initialRights[5] = castlingRights[moveNum][5] = k;
3697     } else { int r;
3698         r = castlingRights[moveNum][0] = initialRights[0];
3699         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3700         r = castlingRights[moveNum][1] = initialRights[1];
3701         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3702         r = castlingRights[moveNum][3] = initialRights[3];
3703         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3704         r = castlingRights[moveNum][4] = initialRights[4];
3705         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3706         /* wildcastle kludge: always assume King has rights */
3707         r = castlingRights[moveNum][2] = initialRights[2];
3708         r = castlingRights[moveNum][5] = initialRights[5];
3709     }
3710     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3711     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3712
3713     
3714     if (ics_getting_history == H_GOT_REQ_HEADER ||
3715         ics_getting_history == H_GOT_UNREQ_HEADER) {
3716         /* This was an initial position from a move list, not
3717            the current position */
3718         return;
3719     }
3720     
3721     /* Update currentMove and known move number limits */
3722     newMove = newGame || moveNum > forwardMostMove;
3723
3724     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3725     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3726         takeback = forwardMostMove - moveNum;
3727         for (i = 0; i < takeback; i++) {
3728              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3729              SendToProgram("undo\n", &first);
3730         }
3731     }
3732
3733     if (newGame) {
3734         forwardMostMove = backwardMostMove = currentMove = moveNum;
3735         if (gameMode == IcsExamining && moveNum == 0) {
3736           /* Workaround for ICS limitation: we are not told the wild
3737              type when starting to examine a game.  But if we ask for
3738              the move list, the move list header will tell us */
3739             ics_getting_history = H_REQUESTED;
3740             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3741             SendToICS(str);
3742         }
3743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3745         forwardMostMove = moveNum;
3746         if (!pausing || currentMove > forwardMostMove)
3747           currentMove = forwardMostMove;
3748     } else {
3749         /* New part of history that is not contiguous with old part */ 
3750         if (pausing && gameMode == IcsExamining) {
3751             pauseExamInvalid = TRUE;
3752             forwardMostMove = pauseExamForwardMostMove;
3753             return;
3754         }
3755         forwardMostMove = backwardMostMove = currentMove = moveNum;
3756         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3757             ics_getting_history = H_REQUESTED;
3758             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3759             SendToICS(str);
3760         }
3761     }
3762     
3763     /* Update the clocks */
3764     if (strchr(elapsed_time, '.')) {
3765       /* Time is in ms */
3766       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3767       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3768     } else {
3769       /* Time is in seconds */
3770       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3771       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3772     }
3773       
3774
3775 #if ZIPPY
3776     if (appData.zippyPlay && newGame &&
3777         gameMode != IcsObserving && gameMode != IcsIdle &&
3778         gameMode != IcsExamining)
3779       ZippyFirstBoard(moveNum, basetime, increment);
3780 #endif
3781     
3782     /* Put the move on the move list, first converting
3783        to canonical algebraic form. */
3784     if (moveNum > 0) {
3785   if (appData.debugMode) {
3786     if (appData.debugMode) { int f = forwardMostMove;
3787         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3788                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3789     }
3790     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3791     fprintf(debugFP, "moveNum = %d\n", moveNum);
3792     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3793     setbuf(debugFP, NULL);
3794   }
3795         if (moveNum <= backwardMostMove) {
3796             /* We don't know what the board looked like before
3797                this move.  Punt. */
3798             strcpy(parseList[moveNum - 1], move_str);
3799             strcat(parseList[moveNum - 1], " ");
3800             strcat(parseList[moveNum - 1], elapsed_time);
3801             moveList[moveNum - 1][0] = NULLCHAR;
3802         } else if (strcmp(move_str, "none") == 0) {
3803             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3804             /* Again, we don't know what the board looked like;
3805                this is really the start of the game. */
3806             parseList[moveNum - 1][0] = NULLCHAR;
3807             moveList[moveNum - 1][0] = NULLCHAR;
3808             backwardMostMove = moveNum;
3809             startedFromSetupPosition = TRUE;
3810             fromX = fromY = toX = toY = -1;
3811         } else {
3812           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3813           //                 So we parse the long-algebraic move string in stead of the SAN move
3814           int valid; char buf[MSG_SIZ], *prom;
3815
3816           // str looks something like "Q/a1-a2"; kill the slash
3817           if(str[1] == '/') 
3818                 sprintf(buf, "%c%s", str[0], str+2);
3819           else  strcpy(buf, str); // might be castling
3820           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3821                 strcat(buf, prom); // long move lacks promo specification!
3822           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3823                 if(appData.debugMode) 
3824                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3825                 strcpy(move_str, buf);
3826           }
3827           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3828                                 &fromX, &fromY, &toX, &toY, &promoChar)
3829                || ParseOneMove(buf, moveNum - 1, &moveType,
3830                                 &fromX, &fromY, &toX, &toY, &promoChar);
3831           // end of long SAN patch
3832           if (valid) {
3833             (void) CoordsToAlgebraic(boards[moveNum - 1],
3834                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3835                                      fromY, fromX, toY, toX, promoChar,
3836                                      parseList[moveNum-1]);
3837             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3838                              castlingRights[moveNum]) ) {
3839               case MT_NONE:
3840               case MT_STALEMATE:
3841               default:
3842                 break;
3843               case MT_CHECK:
3844                 if(gameInfo.variant != VariantShogi)
3845                     strcat(parseList[moveNum - 1], "+");
3846                 break;
3847               case MT_CHECKMATE:
3848               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3849                 strcat(parseList[moveNum - 1], "#");
3850                 break;
3851             }
3852             strcat(parseList[moveNum - 1], " ");
3853             strcat(parseList[moveNum - 1], elapsed_time);
3854             /* currentMoveString is set as a side-effect of ParseOneMove */
3855             strcpy(moveList[moveNum - 1], currentMoveString);
3856             strcat(moveList[moveNum - 1], "\n");
3857           } else {
3858             /* Move from ICS was illegal!?  Punt. */
3859   if (appData.debugMode) {
3860     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3861     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3862   }
3863             strcpy(parseList[moveNum - 1], move_str);
3864             strcat(parseList[moveNum - 1], " ");
3865             strcat(parseList[moveNum - 1], elapsed_time);
3866             moveList[moveNum - 1][0] = NULLCHAR;
3867             fromX = fromY = toX = toY = -1;
3868           }
3869         }
3870   if (appData.debugMode) {
3871     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3872     setbuf(debugFP, NULL);
3873   }
3874
3875 #if ZIPPY
3876         /* Send move to chess program (BEFORE animating it). */
3877         if (appData.zippyPlay && !newGame && newMove && 
3878            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3879
3880             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3881                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3882                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3883                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3884                             move_str);
3885                     DisplayError(str, 0);
3886                 } else {
3887                     if (first.sendTime) {
3888                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3889                     }
3890                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3891                     if (firstMove && !bookHit) {
3892                         firstMove = FALSE;
3893                         if (first.useColors) {
3894                           SendToProgram(gameMode == IcsPlayingWhite ?
3895                                         "white\ngo\n" :
3896                                         "black\ngo\n", &first);
3897                         } else {
3898                           SendToProgram("go\n", &first);
3899                         }
3900                         first.maybeThinking = TRUE;
3901                     }
3902                 }
3903             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3904               if (moveList[moveNum - 1][0] == NULLCHAR) {
3905                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3906                 DisplayError(str, 0);
3907               } else {
3908                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3909                 SendMoveToProgram(moveNum - 1, &first);
3910               }
3911             }
3912         }
3913 #endif
3914     }
3915
3916     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3917         /* If move comes from a remote source, animate it.  If it
3918            isn't remote, it will have already been animated. */
3919         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3920             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3921         }
3922         if (!pausing && appData.highlightLastMove) {
3923             SetHighlights(fromX, fromY, toX, toY);
3924         }
3925     }
3926     
3927     /* Start the clocks */
3928     whiteFlag = blackFlag = FALSE;
3929     appData.clockMode = !(basetime == 0 && increment == 0);
3930     if (ticking == 0) {
3931       ics_clock_paused = TRUE;
3932       StopClocks();
3933     } else if (ticking == 1) {
3934       ics_clock_paused = FALSE;
3935     }
3936     if (gameMode == IcsIdle ||
3937         relation == RELATION_OBSERVING_STATIC ||
3938         relation == RELATION_EXAMINING ||
3939         ics_clock_paused)
3940       DisplayBothClocks();
3941     else
3942       StartClocks();
3943     
3944     /* Display opponents and material strengths */
3945     if (gameInfo.variant != VariantBughouse &&
3946         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3947         if (tinyLayout || smallLayout) {
3948             if(gameInfo.variant == VariantNormal)
3949                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3950                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3951                     basetime, increment);
3952             else
3953                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3954                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3955                     basetime, increment, (int) gameInfo.variant);
3956         } else {
3957             if(gameInfo.variant == VariantNormal)
3958                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3959                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3960                     basetime, increment);
3961             else
3962                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3963                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3964                     basetime, increment, VariantName(gameInfo.variant));
3965         }
3966         DisplayTitle(str);
3967   if (appData.debugMode) {
3968     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3969   }
3970     }
3971
3972    
3973     /* Display the board */
3974     if (!pausing && !appData.noGUI) {
3975       
3976       if (appData.premove)
3977           if (!gotPremove || 
3978              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3979              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3980               ClearPremoveHighlights();
3981
3982       DrawPosition(FALSE, boards[currentMove]);
3983       DisplayMove(moveNum - 1);
3984       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3985             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3986               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3987         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3988       }
3989     }
3990
3991     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3992 #if ZIPPY
3993     if(bookHit) { // [HGM] book: simulate book reply
3994         static char bookMove[MSG_SIZ]; // a bit generous?
3995
3996         programStats.nodes = programStats.depth = programStats.time = 
3997         programStats.score = programStats.got_only_move = 0;
3998         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3999
4000         strcpy(bookMove, "move ");
4001         strcat(bookMove, bookHit);
4002         HandleMachineMove(bookMove, &first);
4003     }
4004 #endif
4005 }
4006
4007 void
4008 GetMoveListEvent()
4009 {
4010     char buf[MSG_SIZ];
4011     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4012         ics_getting_history = H_REQUESTED;
4013         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4014         SendToICS(buf);
4015     }
4016 }
4017
4018 void
4019 AnalysisPeriodicEvent(force)
4020      int force;
4021 {
4022     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4023          && !force) || !appData.periodicUpdates)
4024       return;
4025
4026     /* Send . command to Crafty to collect stats */
4027     SendToProgram(".\n", &first);
4028
4029     /* Don't send another until we get a response (this makes
4030        us stop sending to old Crafty's which don't understand
4031        the "." command (sending illegal cmds resets node count & time,
4032        which looks bad)) */
4033     programStats.ok_to_send = 0;
4034 }
4035
4036 void ics_update_width(new_width)
4037         int new_width;
4038 {
4039         ics_printf("set width %d\n", new_width);
4040 }
4041
4042 void
4043 SendMoveToProgram(moveNum, cps)
4044      int moveNum;
4045      ChessProgramState *cps;
4046 {
4047     char buf[MSG_SIZ];
4048
4049     if (cps->useUsermove) {
4050       SendToProgram("usermove ", cps);
4051     }
4052     if (cps->useSAN) {
4053       char *space;
4054       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4055         int len = space - parseList[moveNum];
4056         memcpy(buf, parseList[moveNum], len);
4057         buf[len++] = '\n';
4058         buf[len] = NULLCHAR;
4059       } else {
4060         sprintf(buf, "%s\n", parseList[moveNum]);
4061       }
4062       SendToProgram(buf, cps);
4063     } else {
4064       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4065         AlphaRank(moveList[moveNum], 4);
4066         SendToProgram(moveList[moveNum], cps);
4067         AlphaRank(moveList[moveNum], 4); // and back
4068       } else
4069       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4070        * the engine. It would be nice to have a better way to identify castle 
4071        * moves here. */
4072       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4073                                                                          && cps->useOOCastle) {
4074         int fromX = moveList[moveNum][0] - AAA; 
4075         int fromY = moveList[moveNum][1] - ONE;
4076         int toX = moveList[moveNum][2] - AAA; 
4077         int toY = moveList[moveNum][3] - ONE;
4078         if((boards[moveNum][fromY][fromX] == WhiteKing 
4079             && boards[moveNum][toY][toX] == WhiteRook)
4080            || (boards[moveNum][fromY][fromX] == BlackKing 
4081                && boards[moveNum][toY][toX] == BlackRook)) {
4082           if(toX > fromX) SendToProgram("O-O\n", cps);
4083           else SendToProgram("O-O-O\n", cps);
4084         }
4085         else SendToProgram(moveList[moveNum], cps);
4086       }
4087       else SendToProgram(moveList[moveNum], cps);
4088       /* End of additions by Tord */
4089     }
4090
4091     /* [HGM] setting up the opening has brought engine in force mode! */
4092     /*       Send 'go' if we are in a mode where machine should play. */
4093     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4094         (gameMode == TwoMachinesPlay   ||
4095 #ifdef ZIPPY
4096          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4097 #endif
4098          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4099         SendToProgram("go\n", cps);
4100   if (appData.debugMode) {
4101     fprintf(debugFP, "(extra)\n");
4102   }
4103     }
4104     setboardSpoiledMachineBlack = 0;
4105 }
4106
4107 void
4108 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4109      ChessMove moveType;
4110      int fromX, fromY, toX, toY;
4111 {
4112     char user_move[MSG_SIZ];
4113
4114     switch (moveType) {
4115       default:
4116         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4117                 (int)moveType, fromX, fromY, toX, toY);
4118         DisplayError(user_move + strlen("say "), 0);
4119         break;
4120       case WhiteKingSideCastle:
4121       case BlackKingSideCastle:
4122       case WhiteQueenSideCastleWild:
4123       case BlackQueenSideCastleWild:
4124       /* PUSH Fabien */
4125       case WhiteHSideCastleFR:
4126       case BlackHSideCastleFR:
4127       /* POP Fabien */
4128         sprintf(user_move, "o-o\n");
4129         break;
4130       case WhiteQueenSideCastle:
4131       case BlackQueenSideCastle:
4132       case WhiteKingSideCastleWild:
4133       case BlackKingSideCastleWild:
4134       /* PUSH Fabien */
4135       case WhiteASideCastleFR:
4136       case BlackASideCastleFR:
4137       /* POP Fabien */
4138         sprintf(user_move, "o-o-o\n");
4139         break;
4140       case WhitePromotionQueen:
4141       case BlackPromotionQueen:
4142       case WhitePromotionRook:
4143       case BlackPromotionRook:
4144       case WhitePromotionBishop:
4145       case BlackPromotionBishop:
4146       case WhitePromotionKnight:
4147       case BlackPromotionKnight:
4148       case WhitePromotionKing:
4149       case BlackPromotionKing:
4150       case WhitePromotionChancellor:
4151       case BlackPromotionChancellor:
4152       case WhitePromotionArchbishop:
4153       case BlackPromotionArchbishop:
4154         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4155             sprintf(user_move, "%c%c%c%c=%c\n",
4156                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4157                 PieceToChar(WhiteFerz));
4158         else if(gameInfo.variant == VariantGreat)
4159             sprintf(user_move, "%c%c%c%c=%c\n",
4160                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4161                 PieceToChar(WhiteMan));
4162         else
4163             sprintf(user_move, "%c%c%c%c=%c\n",
4164                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4165                 PieceToChar(PromoPiece(moveType)));
4166         break;
4167       case WhiteDrop:
4168       case BlackDrop:
4169         sprintf(user_move, "%c@%c%c\n",
4170                 ToUpper(PieceToChar((ChessSquare) fromX)),
4171                 AAA + toX, ONE + toY);
4172         break;
4173       case NormalMove:
4174       case WhiteCapturesEnPassant:
4175       case BlackCapturesEnPassant:
4176       case IllegalMove:  /* could be a variant we don't quite understand */
4177         sprintf(user_move, "%c%c%c%c\n",
4178                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4179         break;
4180     }
4181     SendToICS(user_move);
4182     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4183         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4184 }
4185
4186 void
4187 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4188      int rf, ff, rt, ft;
4189      char promoChar;
4190      char move[7];
4191 {
4192     if (rf == DROP_RANK) {
4193         sprintf(move, "%c@%c%c\n",
4194                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4195     } else {
4196         if (promoChar == 'x' || promoChar == NULLCHAR) {
4197             sprintf(move, "%c%c%c%c\n",
4198                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4199         } else {
4200             sprintf(move, "%c%c%c%c%c\n",
4201                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4202         }
4203     }
4204 }
4205
4206 void
4207 ProcessICSInitScript(f)
4208      FILE *f;
4209 {
4210     char buf[MSG_SIZ];
4211
4212     while (fgets(buf, MSG_SIZ, f)) {
4213         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4214     }
4215
4216     fclose(f);
4217 }
4218
4219
4220 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4221 void
4222 AlphaRank(char *move, int n)
4223 {
4224 //    char *p = move, c; int x, y;
4225
4226     if (appData.debugMode) {
4227         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4228     }
4229
4230     if(move[1]=='*' && 
4231        move[2]>='0' && move[2]<='9' &&
4232        move[3]>='a' && move[3]<='x'    ) {
4233         move[1] = '@';
4234         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4235         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4236     } else
4237     if(move[0]>='0' && move[0]<='9' &&
4238        move[1]>='a' && move[1]<='x' &&
4239        move[2]>='0' && move[2]<='9' &&
4240        move[3]>='a' && move[3]<='x'    ) {
4241         /* input move, Shogi -> normal */
4242         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4243         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4244         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4245         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4246     } else
4247     if(move[1]=='@' &&
4248        move[3]>='0' && move[3]<='9' &&
4249        move[2]>='a' && move[2]<='x'    ) {
4250         move[1] = '*';
4251         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4252         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4253     } else
4254     if(
4255        move[0]>='a' && move[0]<='x' &&
4256        move[3]>='0' && move[3]<='9' &&
4257        move[2]>='a' && move[2]<='x'    ) {
4258          /* output move, normal -> Shogi */
4259         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4260         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4261         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4262         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4263         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4264     }
4265     if (appData.debugMode) {
4266         fprintf(debugFP, "   out = '%s'\n", move);
4267     }
4268 }
4269
4270 /* Parser for moves from gnuchess, ICS, or user typein box */
4271 Boolean
4272 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4273      char *move;
4274      int moveNum;
4275      ChessMove *moveType;
4276      int *fromX, *fromY, *toX, *toY;
4277      char *promoChar;
4278 {       
4279     if (appData.debugMode) {
4280         fprintf(debugFP, "move to parse: %s\n", move);
4281     }
4282     *moveType = yylexstr(moveNum, move);
4283
4284     switch (*moveType) {
4285       case WhitePromotionChancellor:
4286       case BlackPromotionChancellor:
4287       case WhitePromotionArchbishop:
4288       case BlackPromotionArchbishop:
4289       case WhitePromotionQueen:
4290       case BlackPromotionQueen:
4291       case WhitePromotionRook:
4292       case BlackPromotionRook:
4293       case WhitePromotionBishop:
4294       case BlackPromotionBishop:
4295       case WhitePromotionKnight:
4296       case BlackPromotionKnight:
4297       case WhitePromotionKing:
4298       case BlackPromotionKing:
4299       case NormalMove:
4300       case WhiteCapturesEnPassant:
4301       case BlackCapturesEnPassant:
4302       case WhiteKingSideCastle:
4303       case WhiteQueenSideCastle:
4304       case BlackKingSideCastle:
4305       case BlackQueenSideCastle:
4306       case WhiteKingSideCastleWild:
4307       case WhiteQueenSideCastleWild:
4308       case BlackKingSideCastleWild:
4309       case BlackQueenSideCastleWild:
4310       /* Code added by Tord: */
4311       case WhiteHSideCastleFR:
4312       case WhiteASideCastleFR:
4313       case BlackHSideCastleFR:
4314       case BlackASideCastleFR:
4315       /* End of code added by Tord */
4316       case IllegalMove:         /* bug or odd chess variant */
4317         *fromX = currentMoveString[0] - AAA;
4318         *fromY = currentMoveString[1] - ONE;
4319         *toX = currentMoveString[2] - AAA;
4320         *toY = currentMoveString[3] - ONE;
4321         *promoChar = currentMoveString[4];
4322         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4323             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4324     if (appData.debugMode) {
4325         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4326     }
4327             *fromX = *fromY = *toX = *toY = 0;
4328             return FALSE;
4329         }
4330         if (appData.testLegality) {
4331           return (*moveType != IllegalMove);
4332         } else {
4333           return !(fromX == fromY && toX == toY);
4334         }
4335
4336       case WhiteDrop:
4337       case BlackDrop:
4338         *fromX = *moveType == WhiteDrop ?
4339           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4340           (int) CharToPiece(ToLower(currentMoveString[0]));
4341         *fromY = DROP_RANK;
4342         *toX = currentMoveString[2] - AAA;
4343         *toY = currentMoveString[3] - ONE;
4344         *promoChar = NULLCHAR;
4345         return TRUE;
4346
4347       case AmbiguousMove:
4348       case ImpossibleMove:
4349       case (ChessMove) 0:       /* end of file */
4350       case ElapsedTime:
4351       case Comment:
4352       case PGNTag:
4353       case NAG:
4354       case WhiteWins:
4355       case BlackWins:
4356       case GameIsDrawn:
4357       default:
4358     if (appData.debugMode) {
4359         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4360     }
4361         /* bug? */
4362         *fromX = *fromY = *toX = *toY = 0;
4363         *promoChar = NULLCHAR;
4364         return FALSE;
4365     }
4366 }
4367
4368 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4369 // All positions will have equal probability, but the current method will not provide a unique
4370 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4371 #define DARK 1
4372 #define LITE 2
4373 #define ANY 3
4374
4375 int squaresLeft[4];
4376 int piecesLeft[(int)BlackPawn];
4377 int seed, nrOfShuffles;
4378
4379 void GetPositionNumber()
4380 {       // sets global variable seed
4381         int i;
4382
4383         seed = appData.defaultFrcPosition;
4384         if(seed < 0) { // randomize based on time for negative FRC position numbers
4385                 for(i=0; i<50; i++) seed += random();
4386                 seed = random() ^ random() >> 8 ^ random() << 8;
4387                 if(seed<0) seed = -seed;
4388         }
4389 }
4390
4391 int put(Board board, int pieceType, int rank, int n, int shade)
4392 // put the piece on the (n-1)-th empty squares of the given shade
4393 {
4394         int i;
4395
4396         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4397                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4398                         board[rank][i] = (ChessSquare) pieceType;
4399                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4400                         squaresLeft[ANY]--;
4401                         piecesLeft[pieceType]--; 
4402                         return i;
4403                 }
4404         }
4405         return -1;
4406 }
4407
4408
4409 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4410 // calculate where the next piece goes, (any empty square), and put it there
4411 {
4412         int i;
4413
4414         i = seed % squaresLeft[shade];
4415         nrOfShuffles *= squaresLeft[shade];
4416         seed /= squaresLeft[shade];
4417         put(board, pieceType, rank, i, shade);
4418 }
4419
4420 void AddTwoPieces(Board board, int pieceType, int rank)
4421 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4422 {
4423         int i, n=squaresLeft[ANY], j=n-1, k;
4424
4425         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4426         i = seed % k;  // pick one
4427         nrOfShuffles *= k;
4428         seed /= k;
4429         while(i >= j) i -= j--;
4430         j = n - 1 - j; i += j;
4431         put(board, pieceType, rank, j, ANY);
4432         put(board, pieceType, rank, i, ANY);
4433 }
4434
4435 void SetUpShuffle(Board board, int number)
4436 {
4437         int i, p, first=1;
4438
4439         GetPositionNumber(); nrOfShuffles = 1;
4440
4441         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4442         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4443         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4444
4445         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4446
4447         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4448             p = (int) board[0][i];
4449             if(p < (int) BlackPawn) piecesLeft[p] ++;
4450             board[0][i] = EmptySquare;
4451         }
4452
4453         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4454             // shuffles restricted to allow normal castling put KRR first
4455             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4456                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4457             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4458                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4459             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4460                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4461             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4462                 put(board, WhiteRook, 0, 0, ANY);
4463             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4464         }
4465
4466         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4467             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4468             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4469                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4470                 while(piecesLeft[p] >= 2) {
4471                     AddOnePiece(board, p, 0, LITE);
4472                     AddOnePiece(board, p, 0, DARK);
4473                 }
4474                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4475             }
4476
4477         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4478             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4479             // but we leave King and Rooks for last, to possibly obey FRC restriction
4480             if(p == (int)WhiteRook) continue;
4481             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4482             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4483         }
4484
4485         // now everything is placed, except perhaps King (Unicorn) and Rooks
4486
4487         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4488             // Last King gets castling rights
4489             while(piecesLeft[(int)WhiteUnicorn]) {
4490                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4491                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4492             }
4493
4494             while(piecesLeft[(int)WhiteKing]) {
4495                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4496                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4497             }
4498
4499
4500         } else {
4501             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4502             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4503         }
4504
4505         // Only Rooks can be left; simply place them all
4506         while(piecesLeft[(int)WhiteRook]) {
4507                 i = put(board, WhiteRook, 0, 0, ANY);
4508                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4509                         if(first) {
4510                                 first=0;
4511                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4512                         }
4513                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4514                 }
4515         }
4516         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4517             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4518         }
4519
4520         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4521 }
4522
4523 int SetCharTable( char *table, const char * map )
4524 /* [HGM] moved here from winboard.c because of its general usefulness */
4525 /*       Basically a safe strcpy that uses the last character as King */
4526 {
4527     int result = FALSE; int NrPieces;
4528
4529     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4530                     && NrPieces >= 12 && !(NrPieces&1)) {
4531         int i; /* [HGM] Accept even length from 12 to 34 */
4532
4533         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4534         for( i=0; i<NrPieces/2-1; i++ ) {
4535             table[i] = map[i];
4536             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4537         }
4538         table[(int) WhiteKing]  = map[NrPieces/2-1];
4539         table[(int) BlackKing]  = map[NrPieces-1];
4540
4541         result = TRUE;
4542     }
4543
4544     return result;
4545 }
4546
4547 void Prelude(Board board)
4548 {       // [HGM] superchess: random selection of exo-pieces
4549         int i, j, k; ChessSquare p; 
4550         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4551
4552         GetPositionNumber(); // use FRC position number
4553
4554         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4555             SetCharTable(pieceToChar, appData.pieceToCharTable);
4556             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4557                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4558         }
4559
4560         j = seed%4;                 seed /= 4; 
4561         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4562         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4563         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4564         j = seed%3 + (seed%3 >= j); seed /= 3; 
4565         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4566         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4567         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4568         j = seed%3;                 seed /= 3; 
4569         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4570         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4571         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4572         j = seed%2 + (seed%2 >= j); seed /= 2; 
4573         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4574         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4575         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4576         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4577         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4578         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4579         put(board, exoPieces[0],    0, 0, ANY);
4580         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4581 }
4582
4583 void
4584 InitPosition(redraw)
4585      int redraw;
4586 {
4587     ChessSquare (* pieces)[BOARD_SIZE];
4588     int i, j, pawnRow, overrule,
4589     oldx = gameInfo.boardWidth,
4590     oldy = gameInfo.boardHeight,
4591     oldh = gameInfo.holdingsWidth,
4592     oldv = gameInfo.variant;
4593
4594     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4595
4596     /* [AS] Initialize pv info list [HGM] and game status */
4597     {
4598         for( i=0; i<MAX_MOVES; i++ ) {
4599             pvInfoList[i].depth = 0;
4600             epStatus[i]=EP_NONE;
4601             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4602         }
4603
4604         initialRulePlies = 0; /* 50-move counter start */
4605
4606         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4607         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4608     }
4609
4610     
4611     /* [HGM] logic here is completely changed. In stead of full positions */
4612     /* the initialized data only consist of the two backranks. The switch */
4613     /* selects which one we will use, which is than copied to the Board   */
4614     /* initialPosition, which for the rest is initialized by Pawns and    */
4615     /* empty squares. This initial position is then copied to boards[0],  */
4616     /* possibly after shuffling, so that it remains available.            */
4617
4618     gameInfo.holdingsWidth = 0; /* default board sizes */
4619     gameInfo.boardWidth    = 8;
4620     gameInfo.boardHeight   = 8;
4621     gameInfo.holdingsSize  = 0;
4622     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4623     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4624     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4625
4626     switch (gameInfo.variant) {
4627     case VariantFischeRandom:
4628       shuffleOpenings = TRUE;
4629     default:
4630       pieces = FIDEArray;
4631       break;
4632     case VariantShatranj:
4633       pieces = ShatranjArray;
4634       nrCastlingRights = 0;
4635       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4636       break;
4637     case VariantTwoKings:
4638       pieces = twoKingsArray;
4639       break;
4640     case VariantCapaRandom:
4641       shuffleOpenings = TRUE;
4642     case VariantCapablanca:
4643       pieces = CapablancaArray;
4644       gameInfo.boardWidth = 10;
4645       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4646       break;
4647     case VariantGothic:
4648       pieces = GothicArray;
4649       gameInfo.boardWidth = 10;
4650       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4651       break;
4652     case VariantJanus:
4653       pieces = JanusArray;
4654       gameInfo.boardWidth = 10;
4655       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4656       nrCastlingRights = 6;
4657         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4658         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4659         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4660         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4661         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4662         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4663       break;
4664     case VariantFalcon:
4665       pieces = FalconArray;
4666       gameInfo.boardWidth = 10;
4667       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4668       break;
4669     case VariantXiangqi:
4670       pieces = XiangqiArray;
4671       gameInfo.boardWidth  = 9;
4672       gameInfo.boardHeight = 10;
4673       nrCastlingRights = 0;
4674       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4675       break;
4676     case VariantShogi:
4677       pieces = ShogiArray;
4678       gameInfo.boardWidth  = 9;
4679       gameInfo.boardHeight = 9;
4680       gameInfo.holdingsSize = 7;
4681       nrCastlingRights = 0;
4682       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4683       break;
4684     case VariantCourier:
4685       pieces = CourierArray;
4686       gameInfo.boardWidth  = 12;
4687       nrCastlingRights = 0;
4688       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4689       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4690       break;
4691     case VariantKnightmate:
4692       pieces = KnightmateArray;
4693       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4694       break;
4695     case VariantFairy:
4696       pieces = fairyArray;
4697       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4698       break;
4699     case VariantGreat:
4700       pieces = GreatArray;
4701       gameInfo.boardWidth = 10;
4702       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4703       gameInfo.holdingsSize = 8;
4704       break;
4705     case VariantSuper:
4706       pieces = FIDEArray;
4707       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4708       gameInfo.holdingsSize = 8;
4709       startedFromSetupPosition = TRUE;
4710       break;
4711     case VariantCrazyhouse:
4712     case VariantBughouse:
4713       pieces = FIDEArray;
4714       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4715       gameInfo.holdingsSize = 5;
4716       break;
4717     case VariantWildCastle:
4718       pieces = FIDEArray;
4719       /* !!?shuffle with kings guaranteed to be on d or e file */
4720       shuffleOpenings = 1;
4721       break;
4722     case VariantNoCastle:
4723       pieces = FIDEArray;
4724       nrCastlingRights = 0;
4725       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4726       /* !!?unconstrained back-rank shuffle */
4727       shuffleOpenings = 1;
4728       break;
4729     }
4730
4731     overrule = 0;
4732     if(appData.NrFiles >= 0) {
4733         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4734         gameInfo.boardWidth = appData.NrFiles;
4735     }
4736     if(appData.NrRanks >= 0) {
4737         gameInfo.boardHeight = appData.NrRanks;
4738     }
4739     if(appData.holdingsSize >= 0) {
4740         i = appData.holdingsSize;
4741         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4742         gameInfo.holdingsSize = i;
4743     }
4744     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4745     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4746         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4747
4748     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4749     if(pawnRow < 1) pawnRow = 1;
4750
4751     /* User pieceToChar list overrules defaults */
4752     if(appData.pieceToCharTable != NULL)
4753         SetCharTable(pieceToChar, appData.pieceToCharTable);
4754
4755     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4756
4757         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4758             s = (ChessSquare) 0; /* account holding counts in guard band */
4759         for( i=0; i<BOARD_HEIGHT; i++ )
4760             initialPosition[i][j] = s;
4761
4762         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4763         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4764         initialPosition[pawnRow][j] = WhitePawn;
4765         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4766         if(gameInfo.variant == VariantXiangqi) {
4767             if(j&1) {
4768                 initialPosition[pawnRow][j] = 
4769                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4770                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4771                    initialPosition[2][j] = WhiteCannon;
4772                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4773                 }
4774             }
4775         }
4776         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4777     }
4778     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4779
4780             j=BOARD_LEFT+1;
4781             initialPosition[1][j] = WhiteBishop;
4782             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4783             j=BOARD_RGHT-2;
4784             initialPosition[1][j] = WhiteRook;
4785             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4786     }
4787
4788     if( nrCastlingRights == -1) {
4789         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4790         /*       This sets default castling rights from none to normal corners   */
4791         /* Variants with other castling rights must set them themselves above    */
4792         nrCastlingRights = 6;
4793        
4794         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4795         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4796         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4797         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4798         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4799         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4800      }
4801
4802      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4803      if(gameInfo.variant == VariantGreat) { // promotion commoners
4804         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4805         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4806         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4807         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4808      }
4809   if (appData.debugMode) {
4810     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4811   }
4812     if(shuffleOpenings) {
4813         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4814         startedFromSetupPosition = TRUE;
4815     }
4816     if(startedFromPositionFile) {
4817       /* [HGM] loadPos: use PositionFile for every new game */
4818       CopyBoard(initialPosition, filePosition);
4819       for(i=0; i<nrCastlingRights; i++)
4820           castlingRights[0][i] = initialRights[i] = fileRights[i];
4821       startedFromSetupPosition = TRUE;
4822     }
4823
4824     CopyBoard(boards[0], initialPosition);
4825
4826     if(oldx != gameInfo.boardWidth ||
4827        oldy != gameInfo.boardHeight ||
4828        oldh != gameInfo.holdingsWidth
4829 #ifdef GOTHIC
4830        || oldv == VariantGothic ||        // For licensing popups
4831        gameInfo.variant == VariantGothic
4832 #endif
4833 #ifdef FALCON
4834        || oldv == VariantFalcon ||
4835        gameInfo.variant == VariantFalcon
4836 #endif
4837                                          )
4838             InitDrawingSizes(-2 ,0);
4839
4840     if (redraw)
4841       DrawPosition(TRUE, boards[currentMove]);
4842 }
4843
4844 void
4845 SendBoard(cps, moveNum)
4846      ChessProgramState *cps;
4847      int moveNum;
4848 {
4849     char message[MSG_SIZ];
4850     
4851     if (cps->useSetboard) {
4852       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4853       sprintf(message, "setboard %s\n", fen);
4854       SendToProgram(message, cps);
4855       free(fen);
4856
4857     } else {
4858       ChessSquare *bp;
4859       int i, j;
4860       /* Kludge to set black to move, avoiding the troublesome and now
4861        * deprecated "black" command.
4862        */
4863       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4864
4865       SendToProgram("edit\n", cps);
4866       SendToProgram("#\n", cps);
4867       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868         bp = &boards[moveNum][i][BOARD_LEFT];
4869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870           if ((int) *bp < (int) BlackPawn) {
4871             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4872                     AAA + j, ONE + i);
4873             if(message[0] == '+' || message[0] == '~') {
4874                 sprintf(message, "%c%c%c+\n",
4875                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4876                         AAA + j, ONE + i);
4877             }
4878             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879                 message[1] = BOARD_RGHT   - 1 - j + '1';
4880                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4881             }
4882             SendToProgram(message, cps);
4883           }
4884         }
4885       }
4886     
4887       SendToProgram("c\n", cps);
4888       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4889         bp = &boards[moveNum][i][BOARD_LEFT];
4890         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4891           if (((int) *bp != (int) EmptySquare)
4892               && ((int) *bp >= (int) BlackPawn)) {
4893             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4894                     AAA + j, ONE + i);
4895             if(message[0] == '+' || message[0] == '~') {
4896                 sprintf(message, "%c%c%c+\n",
4897                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4898                         AAA + j, ONE + i);
4899             }
4900             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4901                 message[1] = BOARD_RGHT   - 1 - j + '1';
4902                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4903             }
4904             SendToProgram(message, cps);
4905           }
4906         }
4907       }
4908     
4909       SendToProgram(".\n", cps);
4910     }
4911     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4912 }
4913
4914 int
4915 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4916 {
4917     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4918     /* [HGM] add Shogi promotions */
4919     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4920     ChessSquare piece;
4921     ChessMove moveType;
4922     Boolean premove;
4923
4924     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4925     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4926
4927     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4928       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4929         return FALSE;
4930
4931     piece = boards[currentMove][fromY][fromX];
4932     if(gameInfo.variant == VariantShogi) {
4933         promotionZoneSize = 3;
4934         highestPromotingPiece = (int)WhiteFerz;
4935     }
4936
4937     // next weed out all moves that do not touch the promotion zone at all
4938     if((int)piece >= BlackPawn) {
4939         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4940              return FALSE;
4941         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4942     } else {
4943         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4944            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4945     }
4946
4947     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4948
4949     // weed out mandatory Shogi promotions
4950     if(gameInfo.variant == VariantShogi) {
4951         if(piece >= BlackPawn) {
4952             if(toY == 0 && piece == BlackPawn ||
4953                toY == 0 && piece == BlackQueen ||
4954                toY <= 1 && piece == BlackKnight) {
4955                 *promoChoice = '+';
4956                 return FALSE;
4957             }
4958         } else {
4959             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4960                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4961                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4962                 *promoChoice = '+';
4963                 return FALSE;
4964             }
4965         }
4966     }
4967
4968     // weed out obviously illegal Pawn moves
4969     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4970         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4971         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4972         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4973         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4974         // note we are not allowed to test for valid (non-)capture, due to premove
4975     }
4976
4977     // we either have a choice what to promote to, or (in Shogi) whether to promote
4978     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4979         *promoChoice = PieceToChar(BlackFerz);  // no choice
4980         return FALSE;
4981     }
4982     if(appData.alwaysPromoteToQueen) { // predetermined
4983         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4984              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4985         else *promoChoice = PieceToChar(BlackQueen);
4986         return FALSE;
4987     }
4988
4989     // suppress promotion popup on illegal moves that are not premoves
4990     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4991               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
4992     if(appData.testLegality && !premove) {
4993         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4994                         epStatus[currentMove], castlingRights[currentMove],
4995                         fromY, fromX, toY, toX, NULLCHAR);
4996         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
4997            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4998             return FALSE;
4999     }
5000
5001     return TRUE;
5002 }
5003
5004 int
5005 InPalace(row, column)
5006      int row, column;
5007 {   /* [HGM] for Xiangqi */
5008     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5009          column < (BOARD_WIDTH + 4)/2 &&
5010          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5011     return FALSE;
5012 }
5013
5014 int
5015 PieceForSquare (x, y)
5016      int x;
5017      int y;
5018 {
5019   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5020      return -1;
5021   else
5022      return boards[currentMove][y][x];
5023 }
5024
5025 int
5026 OKToStartUserMove(x, y)
5027      int x, y;
5028 {
5029     ChessSquare from_piece;
5030     int white_piece;
5031
5032     if (matchMode) return FALSE;
5033     if (gameMode == EditPosition) return TRUE;
5034
5035     if (x >= 0 && y >= 0)
5036       from_piece = boards[currentMove][y][x];
5037     else
5038       from_piece = EmptySquare;
5039
5040     if (from_piece == EmptySquare) return FALSE;
5041
5042     white_piece = (int)from_piece >= (int)WhitePawn &&
5043       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5044
5045     switch (gameMode) {
5046       case PlayFromGameFile:
5047       case AnalyzeFile:
5048       case TwoMachinesPlay:
5049       case EndOfGame:
5050         return FALSE;
5051
5052       case IcsObserving:
5053       case IcsIdle:
5054         return FALSE;
5055
5056       case MachinePlaysWhite:
5057       case IcsPlayingBlack:
5058         if (appData.zippyPlay) return FALSE;
5059         if (white_piece) {
5060             DisplayMoveError(_("You are playing Black"));
5061             return FALSE;
5062         }
5063         break;
5064
5065       case MachinePlaysBlack:
5066       case IcsPlayingWhite:
5067         if (appData.zippyPlay) return FALSE;
5068         if (!white_piece) {
5069             DisplayMoveError(_("You are playing White"));
5070             return FALSE;
5071         }
5072         break;
5073
5074       case EditGame:
5075         if (!white_piece && WhiteOnMove(currentMove)) {
5076             DisplayMoveError(_("It is White's turn"));
5077             return FALSE;
5078         }           
5079         if (white_piece && !WhiteOnMove(currentMove)) {
5080             DisplayMoveError(_("It is Black's turn"));
5081             return FALSE;
5082         }           
5083         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5084             /* Editing correspondence game history */
5085             /* Could disallow this or prompt for confirmation */
5086             cmailOldMove = -1;
5087         }
5088         if (currentMove < forwardMostMove) {
5089             /* Discarding moves */
5090             /* Could prompt for confirmation here,
5091                but I don't think that's such a good idea */
5092             forwardMostMove = currentMove;
5093         }
5094         break;
5095
5096       case BeginningOfGame:
5097         if (appData.icsActive) return FALSE;
5098         if (!appData.noChessProgram) {
5099             if (!white_piece) {
5100                 DisplayMoveError(_("You are playing White"));
5101                 return FALSE;
5102             }
5103         }
5104         break;
5105         
5106       case Training:
5107         if (!white_piece && WhiteOnMove(currentMove)) {
5108             DisplayMoveError(_("It is White's turn"));
5109             return FALSE;
5110         }           
5111         if (white_piece && !WhiteOnMove(currentMove)) {
5112             DisplayMoveError(_("It is Black's turn"));
5113             return FALSE;
5114         }           
5115         break;
5116
5117       default:
5118       case IcsExamining:
5119         break;
5120     }
5121     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5122         && gameMode != AnalyzeFile && gameMode != Training) {
5123         DisplayMoveError(_("Displayed position is not current"));
5124         return FALSE;
5125     }
5126     return TRUE;
5127 }
5128
5129 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5130 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5131 int lastLoadGameUseList = FALSE;
5132 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5133 ChessMove lastLoadGameStart = (ChessMove) 0;
5134
5135 ChessMove
5136 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5137      int fromX, fromY, toX, toY;
5138      int promoChar;
5139      Boolean captureOwn;
5140 {
5141     ChessMove moveType;
5142     ChessSquare pdown, pup;
5143
5144     /* Check if the user is playing in turn.  This is complicated because we
5145        let the user "pick up" a piece before it is his turn.  So the piece he
5146        tried to pick up may have been captured by the time he puts it down!
5147        Therefore we use the color the user is supposed to be playing in this
5148        test, not the color of the piece that is currently on the starting
5149        square---except in EditGame mode, where the user is playing both
5150        sides; fortunately there the capture race can't happen.  (It can
5151        now happen in IcsExamining mode, but that's just too bad.  The user
5152        will get a somewhat confusing message in that case.)
5153        */
5154
5155     switch (gameMode) {
5156       case PlayFromGameFile:
5157       case AnalyzeFile:
5158       case TwoMachinesPlay:
5159       case EndOfGame:
5160       case IcsObserving:
5161       case IcsIdle:
5162         /* We switched into a game mode where moves are not accepted,
5163            perhaps while the mouse button was down. */
5164         return ImpossibleMove;
5165
5166       case MachinePlaysWhite:
5167         /* User is moving for Black */
5168         if (WhiteOnMove(currentMove)) {
5169             DisplayMoveError(_("It is White's turn"));
5170             return ImpossibleMove;
5171         }
5172         break;
5173
5174       case MachinePlaysBlack:
5175         /* User is moving for White */
5176         if (!WhiteOnMove(currentMove)) {
5177             DisplayMoveError(_("It is Black's turn"));
5178             return ImpossibleMove;
5179         }
5180         break;
5181
5182       case EditGame:
5183       case IcsExamining:
5184       case BeginningOfGame:
5185       case AnalyzeMode:
5186       case Training:
5187         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5188             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5189             /* User is moving for Black */
5190             if (WhiteOnMove(currentMove)) {
5191                 DisplayMoveError(_("It is White's turn"));
5192                 return ImpossibleMove;
5193             }
5194         } else {
5195             /* User is moving for White */
5196             if (!WhiteOnMove(currentMove)) {
5197                 DisplayMoveError(_("It is Black's turn"));
5198                 return ImpossibleMove;
5199             }
5200         }
5201         break;
5202
5203       case IcsPlayingBlack:
5204         /* User is moving for Black */
5205         if (WhiteOnMove(currentMove)) {
5206             if (!appData.premove) {
5207                 DisplayMoveError(_("It is White's turn"));
5208             } else if (toX >= 0 && toY >= 0) {
5209                 premoveToX = toX;
5210                 premoveToY = toY;
5211                 premoveFromX = fromX;
5212                 premoveFromY = fromY;
5213                 premovePromoChar = promoChar;
5214                 gotPremove = 1;
5215                 if (appData.debugMode) 
5216                     fprintf(debugFP, "Got premove: fromX %d,"
5217                             "fromY %d, toX %d, toY %d\n",
5218                             fromX, fromY, toX, toY);
5219             }
5220             return ImpossibleMove;
5221         }
5222         break;
5223
5224       case IcsPlayingWhite:
5225         /* User is moving for White */
5226         if (!WhiteOnMove(currentMove)) {
5227             if (!appData.premove) {
5228                 DisplayMoveError(_("It is Black's turn"));
5229             } else if (toX >= 0 && toY >= 0) {
5230                 premoveToX = toX;
5231                 premoveToY = toY;
5232                 premoveFromX = fromX;
5233                 premoveFromY = fromY;
5234                 premovePromoChar = promoChar;
5235                 gotPremove = 1;
5236                 if (appData.debugMode) 
5237                     fprintf(debugFP, "Got premove: fromX %d,"
5238                             "fromY %d, toX %d, toY %d\n",
5239                             fromX, fromY, toX, toY);
5240             }
5241             return ImpossibleMove;
5242         }
5243         break;
5244
5245       default:
5246         break;
5247
5248       case EditPosition:
5249         /* EditPosition, empty square, or different color piece;
5250            click-click move is possible */
5251         if (toX == -2 || toY == -2) {
5252             boards[0][fromY][fromX] = EmptySquare;
5253             return AmbiguousMove;
5254         } else if (toX >= 0 && toY >= 0) {
5255             boards[0][toY][toX] = boards[0][fromY][fromX];
5256             boards[0][fromY][fromX] = EmptySquare;
5257             return AmbiguousMove;
5258         }
5259         return ImpossibleMove;
5260     }
5261
5262     pdown = boards[currentMove][fromY][fromX];
5263     pup = boards[currentMove][toY][toX];
5264
5265     /* [HGM] If move started in holdings, it means a drop */
5266     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5267          if( pup != EmptySquare ) return ImpossibleMove;
5268          if(appData.testLegality) {
5269              /* it would be more logical if LegalityTest() also figured out
5270               * which drops are legal. For now we forbid pawns on back rank.
5271               * Shogi is on its own here...
5272               */
5273              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5274                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5275                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5276          }
5277          return WhiteDrop; /* Not needed to specify white or black yet */
5278     }
5279
5280     userOfferedDraw = FALSE;
5281         
5282     /* [HGM] always test for legality, to get promotion info */
5283     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5284                           epStatus[currentMove], castlingRights[currentMove],
5285                                          fromY, fromX, toY, toX, promoChar);
5286     /* [HGM] but possibly ignore an IllegalMove result */
5287     if (appData.testLegality) {
5288         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5289             DisplayMoveError(_("Illegal move"));
5290             return ImpossibleMove;
5291         }
5292     }
5293 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5294     return moveType;
5295     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5296        function is made into one that returns an OK move type if FinishMove
5297        should be called. This to give the calling driver routine the
5298        opportunity to finish the userMove input with a promotion popup,
5299        without bothering the user with this for invalid or illegal moves */
5300
5301 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5302 }
5303
5304 /* Common tail of UserMoveEvent and DropMenuEvent */
5305 int
5306 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5307      ChessMove moveType;
5308      int fromX, fromY, toX, toY;
5309      /*char*/int promoChar;
5310 {
5311     char *bookHit = 0;
5312 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5313     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5314         // [HGM] superchess: suppress promotions to non-available piece
5315         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5316         if(WhiteOnMove(currentMove)) {
5317             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5318         } else {
5319             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5320         }
5321     }
5322
5323     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5324        move type in caller when we know the move is a legal promotion */
5325     if(moveType == NormalMove && promoChar)
5326         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5327 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5328     /* [HGM] convert drag-and-drop piece drops to standard form */
5329     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5330          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5331            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5332                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5333 //         fromX = boards[currentMove][fromY][fromX];
5334            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5335            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5336            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5337            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5338          fromY = DROP_RANK;
5339     }
5340
5341     /* [HGM] <popupFix> The following if has been moved here from
5342        UserMoveEvent(). Because it seemed to belon here (why not allow
5343        piece drops in training games?), and because it can only be
5344        performed after it is known to what we promote. */
5345     if (gameMode == Training) {
5346       /* compare the move played on the board to the next move in the
5347        * game. If they match, display the move and the opponent's response. 
5348        * If they don't match, display an error message.
5349        */
5350       int saveAnimate;
5351       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5352       CopyBoard(testBoard, boards[currentMove]);
5353       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5354
5355       if (CompareBoards(testBoard, boards[currentMove+1])) {
5356         ForwardInner(currentMove+1);
5357
5358         /* Autoplay the opponent's response.
5359          * if appData.animate was TRUE when Training mode was entered,
5360          * the response will be animated.
5361          */
5362         saveAnimate = appData.animate;
5363         appData.animate = animateTraining;
5364         ForwardInner(currentMove+1);
5365         appData.animate = saveAnimate;
5366
5367         /* check for the end of the game */
5368         if (currentMove >= forwardMostMove) {
5369           gameMode = PlayFromGameFile;
5370           ModeHighlight();
5371           SetTrainingModeOff();
5372           DisplayInformation(_("End of game"));
5373         }
5374       } else {
5375         DisplayError(_("Incorrect move"), 0);
5376       }
5377       return 1;
5378     }
5379
5380   /* Ok, now we know that the move is good, so we can kill
5381      the previous line in Analysis Mode */
5382   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5383     forwardMostMove = currentMove;
5384   }
5385
5386   /* If we need the chess program but it's dead, restart it */
5387   ResurrectChessProgram();
5388
5389   /* A user move restarts a paused game*/
5390   if (pausing)
5391     PauseEvent();
5392
5393   thinkOutput[0] = NULLCHAR;
5394
5395   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5396
5397   if (gameMode == BeginningOfGame) {
5398     if (appData.noChessProgram) {
5399       gameMode = EditGame;
5400       SetGameInfo();
5401     } else {
5402       char buf[MSG_SIZ];
5403       gameMode = MachinePlaysBlack;
5404       StartClocks();
5405       SetGameInfo();
5406       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5407       DisplayTitle(buf);
5408       if (first.sendName) {
5409         sprintf(buf, "name %s\n", gameInfo.white);
5410         SendToProgram(buf, &first);
5411       }
5412       StartClocks();
5413     }
5414     ModeHighlight();
5415   }
5416 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5417   /* Relay move to ICS or chess engine */
5418   if (appData.icsActive) {
5419     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5420         gameMode == IcsExamining) {
5421       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5422       ics_user_moved = 1;
5423     }
5424   } else {
5425     if (first.sendTime && (gameMode == BeginningOfGame ||
5426                            gameMode == MachinePlaysWhite ||
5427                            gameMode == MachinePlaysBlack)) {
5428       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5429     }
5430     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5431          // [HGM] book: if program might be playing, let it use book
5432         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5433         first.maybeThinking = TRUE;
5434     } else SendMoveToProgram(forwardMostMove-1, &first);
5435     if (currentMove == cmailOldMove + 1) {
5436       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5437     }
5438   }
5439
5440   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5441
5442   switch (gameMode) {
5443   case EditGame:
5444     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5445                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5446     case MT_NONE:
5447     case MT_CHECK:
5448       break;
5449     case MT_CHECKMATE:
5450     case MT_STAINMATE:
5451       if (WhiteOnMove(currentMove)) {
5452         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5453       } else {
5454         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5455       }
5456       break;
5457     case MT_STALEMATE:
5458       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5459       break;
5460     }
5461     break;
5462     
5463   case MachinePlaysBlack:
5464   case MachinePlaysWhite:
5465     /* disable certain menu options while machine is thinking */
5466     SetMachineThinkingEnables();
5467     break;
5468
5469   default:
5470     break;
5471   }
5472
5473   if(bookHit) { // [HGM] book: simulate book reply
5474         static char bookMove[MSG_SIZ]; // a bit generous?
5475
5476         programStats.nodes = programStats.depth = programStats.time = 
5477         programStats.score = programStats.got_only_move = 0;
5478         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5479
5480         strcpy(bookMove, "move ");
5481         strcat(bookMove, bookHit);
5482         HandleMachineMove(bookMove, &first);
5483   }
5484   return 1;
5485 }
5486
5487 void
5488 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5489      int fromX, fromY, toX, toY;
5490      int promoChar;
5491 {
5492     /* [HGM] This routine was added to allow calling of its two logical
5493        parts from other modules in the old way. Before, UserMoveEvent()
5494        automatically called FinishMove() if the move was OK, and returned
5495        otherwise. I separated the two, in order to make it possible to
5496        slip a promotion popup in between. But that it always needs two
5497        calls, to the first part, (now called UserMoveTest() ), and to
5498        FinishMove if the first part succeeded. Calls that do not need
5499        to do anything in between, can call this routine the old way. 
5500     */
5501     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5502 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5503     if(moveType == AmbiguousMove)
5504         DrawPosition(FALSE, boards[currentMove]);
5505     else if(moveType != ImpossibleMove && moveType != Comment)
5506         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5507 }
5508
5509 void LeftClick(ClickType clickType, int xPix, int yPix)
5510 {
5511     int x, y;
5512     Boolean saveAnimate;
5513     static int second = 0, promotionChoice = 0;
5514     char promoChoice = NULLCHAR;
5515
5516     if (clickType == Press) ErrorPopDown();
5517
5518     x = EventToSquare(xPix, BOARD_WIDTH);
5519     y = EventToSquare(yPix, BOARD_HEIGHT);
5520     if (!flipView && y >= 0) {
5521         y = BOARD_HEIGHT - 1 - y;
5522     }
5523     if (flipView && x >= 0) {
5524         x = BOARD_WIDTH - 1 - x;
5525     }
5526
5527     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5528         if(clickType == Release) return; // ignore upclick of click-click destination
5529         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5530         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5531         if(gameInfo.holdingsWidth && 
5532                 (WhiteOnMove(currentMove) 
5533                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5534                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5535             // click in right holdings, for determining promotion piece
5536             ChessSquare p = boards[currentMove][y][x];
5537             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5538             if(p != EmptySquare) {
5539                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5540                 fromX = fromY = -1;
5541                 return;
5542             }
5543         }
5544         DrawPosition(FALSE, boards[currentMove]);
5545         return;
5546     }
5547
5548     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5549     if(clickType == Press
5550             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5551               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5552               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5553         return;
5554
5555     if (fromX == -1) {
5556         if (clickType == Press) {
5557             /* First square */
5558             if (OKToStartUserMove(x, y)) {
5559                 fromX = x;
5560                 fromY = y;
5561                 second = 0;
5562                 DragPieceBegin(xPix, yPix);
5563                 if (appData.highlightDragging) {
5564                     SetHighlights(x, y, -1, -1);
5565                 }
5566             }
5567         }
5568         return;
5569     }
5570
5571     /* fromX != -1 */
5572     if (clickType == Press && gameMode != EditPosition) {
5573         ChessSquare fromP;
5574         ChessSquare toP;
5575         int frc;
5576
5577         // ignore off-board to clicks
5578         if(y < 0 || x < 0) return;
5579
5580         /* Check if clicking again on the same color piece */
5581         fromP = boards[currentMove][fromY][fromX];
5582         toP = boards[currentMove][y][x];
5583         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5584         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5585              WhitePawn <= toP && toP <= WhiteKing &&
5586              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5587              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5588             (BlackPawn <= fromP && fromP <= BlackKing && 
5589              BlackPawn <= toP && toP <= BlackKing &&
5590              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5591              !(fromP == BlackKing && toP == BlackRook && frc))) {
5592             /* Clicked again on same color piece -- changed his mind */
5593             second = (x == fromX && y == fromY);
5594             if (appData.highlightDragging) {
5595                 SetHighlights(x, y, -1, -1);
5596             } else {
5597                 ClearHighlights();
5598             }
5599             if (OKToStartUserMove(x, y)) {
5600                 fromX = x;
5601                 fromY = y;
5602                 DragPieceBegin(xPix, yPix);
5603             }
5604             return;
5605         }
5606         // ignore to-clicks in holdings
5607         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5608     }
5609
5610     if (clickType == Release && (x == fromX && y == fromY ||
5611         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5612
5613         // treat drags into holding as click on start square
5614         x = fromX; y = fromY;
5615
5616         DragPieceEnd(xPix, yPix);
5617         if (appData.animateDragging) {
5618             /* Undo animation damage if any */
5619             DrawPosition(FALSE, NULL);
5620         }
5621         if (second) {
5622             /* Second up/down in same square; just abort move */
5623             second = 0;
5624             fromX = fromY = -1;
5625             ClearHighlights();
5626             gotPremove = 0;
5627             ClearPremoveHighlights();
5628         } else {
5629             /* First upclick in same square; start click-click mode */
5630             SetHighlights(x, y, -1, -1);
5631         }
5632         return;
5633     }
5634
5635     /* we now have a different from- and to-square */
5636     /* Completed move */
5637     toX = x;
5638     toY = y;
5639     saveAnimate = appData.animate;
5640     if (clickType == Press) {
5641         /* Finish clickclick move */
5642         if (appData.animate || appData.highlightLastMove) {
5643             SetHighlights(fromX, fromY, toX, toY);
5644         } else {
5645             ClearHighlights();
5646         }
5647     } else {
5648         /* Finish drag move */
5649         if (appData.highlightLastMove) {
5650             SetHighlights(fromX, fromY, toX, toY);
5651         } else {
5652             ClearHighlights();
5653         }
5654         DragPieceEnd(xPix, yPix);
5655         /* Don't animate move and drag both */
5656         appData.animate = FALSE;
5657     }
5658     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5659         SetHighlights(fromX, fromY, toX, toY);
5660         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5661             // [HGM] super: promotion to captured piece selected from holdings
5662             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5663             promotionChoice = TRUE;
5664             // kludge follows to temporarily execute move on display, without promoting yet
5665             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5666             boards[currentMove][toY][toX] = p;
5667             DrawPosition(FALSE, boards[currentMove]);
5668             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5669             boards[currentMove][toY][toX] = q;
5670             DisplayMessage("Click in holdings to choose piece", "");
5671             return;
5672         }
5673         PromotionPopUp();
5674     } else {
5675         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5676         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5677         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5678         fromX = fromY = -1;
5679     }
5680     appData.animate = saveAnimate;
5681     if (appData.animate || appData.animateDragging) {
5682         /* Undo animation damage if needed */
5683         DrawPosition(FALSE, NULL);
5684     }
5685 }
5686
5687 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5688 {
5689 //    char * hint = lastHint;
5690     FrontEndProgramStats stats;
5691
5692     stats.which = cps == &first ? 0 : 1;
5693     stats.depth = cpstats->depth;
5694     stats.nodes = cpstats->nodes;
5695     stats.score = cpstats->score;
5696     stats.time = cpstats->time;
5697     stats.pv = cpstats->movelist;
5698     stats.hint = lastHint;
5699     stats.an_move_index = 0;
5700     stats.an_move_count = 0;
5701
5702     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5703         stats.hint = cpstats->move_name;
5704         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5705         stats.an_move_count = cpstats->nr_moves;
5706     }
5707
5708     SetProgramStats( &stats );
5709 }
5710
5711 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5712 {   // [HGM] book: this routine intercepts moves to simulate book replies
5713     char *bookHit = NULL;
5714
5715     //first determine if the incoming move brings opponent into his book
5716     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5717         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5718     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5719     if(bookHit != NULL && !cps->bookSuspend) {
5720         // make sure opponent is not going to reply after receiving move to book position
5721         SendToProgram("force\n", cps);
5722         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5723     }
5724     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5725     // now arrange restart after book miss
5726     if(bookHit) {
5727         // after a book hit we never send 'go', and the code after the call to this routine
5728         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5729         char buf[MSG_SIZ];
5730         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5731         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5732         SendToProgram(buf, cps);
5733         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5734     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5735         SendToProgram("go\n", cps);
5736         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5737     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5738         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5739             SendToProgram("go\n", cps); 
5740         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5741     }
5742     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5743 }
5744
5745 char *savedMessage;
5746 ChessProgramState *savedState;
5747 void DeferredBookMove(void)
5748 {
5749         if(savedState->lastPing != savedState->lastPong)
5750                     ScheduleDelayedEvent(DeferredBookMove, 10);
5751         else
5752         HandleMachineMove(savedMessage, savedState);
5753 }
5754
5755 void
5756 HandleMachineMove(message, cps)
5757      char *message;
5758      ChessProgramState *cps;
5759 {
5760     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5761     char realname[MSG_SIZ];
5762     int fromX, fromY, toX, toY;
5763     ChessMove moveType;
5764     char promoChar;
5765     char *p;
5766     int machineWhite;
5767     char *bookHit;
5768
5769 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5770     /*
5771      * Kludge to ignore BEL characters
5772      */
5773     while (*message == '\007') message++;
5774
5775     /*
5776      * [HGM] engine debug message: ignore lines starting with '#' character
5777      */
5778     if(cps->debug && *message == '#') return;
5779
5780     /*
5781      * Look for book output
5782      */
5783     if (cps == &first && bookRequested) {
5784         if (message[0] == '\t' || message[0] == ' ') {
5785             /* Part of the book output is here; append it */
5786             strcat(bookOutput, message);
5787             strcat(bookOutput, "  \n");
5788             return;
5789         } else if (bookOutput[0] != NULLCHAR) {
5790             /* All of book output has arrived; display it */
5791             char *p = bookOutput;
5792             while (*p != NULLCHAR) {
5793                 if (*p == '\t') *p = ' ';
5794                 p++;
5795             }
5796             DisplayInformation(bookOutput);
5797             bookRequested = FALSE;
5798             /* Fall through to parse the current output */
5799         }
5800     }
5801
5802     /*
5803      * Look for machine move.
5804      */
5805     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5806         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5807     {
5808         /* This method is only useful on engines that support ping */
5809         if (cps->lastPing != cps->lastPong) {
5810           if (gameMode == BeginningOfGame) {
5811             /* Extra move from before last new; ignore */
5812             if (appData.debugMode) {
5813                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5814             }
5815           } else {
5816             if (appData.debugMode) {
5817                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5818                         cps->which, gameMode);
5819             }
5820
5821             SendToProgram("undo\n", cps);
5822           }
5823           return;
5824         }
5825
5826         switch (gameMode) {
5827           case BeginningOfGame:
5828             /* Extra move from before last reset; ignore */
5829             if (appData.debugMode) {
5830                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5831             }
5832             return;
5833
5834           case EndOfGame:
5835           case IcsIdle:
5836           default:
5837             /* Extra move after we tried to stop.  The mode test is
5838                not a reliable way of detecting this problem, but it's
5839                the best we can do on engines that don't support ping.
5840             */
5841             if (appData.debugMode) {
5842                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5843                         cps->which, gameMode);
5844             }
5845             SendToProgram("undo\n", cps);
5846             return;
5847
5848           case MachinePlaysWhite:
5849           case IcsPlayingWhite:
5850             machineWhite = TRUE;
5851             break;
5852
5853           case MachinePlaysBlack:
5854           case IcsPlayingBlack:
5855             machineWhite = FALSE;
5856             break;
5857
5858           case TwoMachinesPlay:
5859             machineWhite = (cps->twoMachinesColor[0] == 'w');
5860             break;
5861         }
5862         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5863             if (appData.debugMode) {
5864                 fprintf(debugFP,
5865                         "Ignoring move out of turn by %s, gameMode %d"
5866                         ", forwardMost %d\n",
5867                         cps->which, gameMode, forwardMostMove);
5868             }
5869             return;
5870         }
5871
5872     if (appData.debugMode) { int f = forwardMostMove;
5873         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5874                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5875     }
5876         if(cps->alphaRank) AlphaRank(machineMove, 4);
5877         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5878                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5879             /* Machine move could not be parsed; ignore it. */
5880             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5881                     machineMove, cps->which);
5882             DisplayError(buf1, 0);
5883             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5884                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5885             if (gameMode == TwoMachinesPlay) {
5886               GameEnds(machineWhite ? BlackWins : WhiteWins,
5887                        buf1, GE_XBOARD);
5888             }
5889             return;
5890         }
5891
5892         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5893         /* So we have to redo legality test with true e.p. status here,  */
5894         /* to make sure an illegal e.p. capture does not slip through,   */
5895         /* to cause a forfeit on a justified illegal-move complaint      */
5896         /* of the opponent.                                              */
5897         if( gameMode==TwoMachinesPlay && appData.testLegality
5898             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5899                                                               ) {
5900            ChessMove moveType;
5901            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5902                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5903                              fromY, fromX, toY, toX, promoChar);
5904             if (appData.debugMode) {
5905                 int i;
5906                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5907                     castlingRights[forwardMostMove][i], castlingRank[i]);
5908                 fprintf(debugFP, "castling rights\n");
5909             }
5910             if(moveType == IllegalMove) {
5911                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5912                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5913                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5914                            buf1, GE_XBOARD);
5915                 return;
5916            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5917            /* [HGM] Kludge to handle engines that send FRC-style castling
5918               when they shouldn't (like TSCP-Gothic) */
5919            switch(moveType) {
5920              case WhiteASideCastleFR:
5921              case BlackASideCastleFR:
5922                toX+=2;
5923                currentMoveString[2]++;
5924                break;
5925              case WhiteHSideCastleFR:
5926              case BlackHSideCastleFR:
5927                toX--;
5928                currentMoveString[2]--;
5929                break;
5930              default: ; // nothing to do, but suppresses warning of pedantic compilers
5931            }
5932         }
5933         hintRequested = FALSE;
5934         lastHint[0] = NULLCHAR;
5935         bookRequested = FALSE;
5936         /* Program may be pondering now */
5937         cps->maybeThinking = TRUE;
5938         if (cps->sendTime == 2) cps->sendTime = 1;
5939         if (cps->offeredDraw) cps->offeredDraw--;
5940
5941 #if ZIPPY
5942         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5943             first.initDone) {
5944           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5945           ics_user_moved = 1;
5946           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5947                 char buf[3*MSG_SIZ];
5948
5949                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5950                         programStats.score / 100.,
5951                         programStats.depth,
5952                         programStats.time / 100.,
5953                         (unsigned int)programStats.nodes,
5954                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5955                         programStats.movelist);
5956                 SendToICS(buf);
5957 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5958           }
5959         }
5960 #endif
5961         /* currentMoveString is set as a side-effect of ParseOneMove */
5962         strcpy(machineMove, currentMoveString);
5963         strcat(machineMove, "\n");
5964         strcpy(moveList[forwardMostMove], machineMove);
5965
5966         /* [AS] Save move info and clear stats for next move */
5967         pvInfoList[ forwardMostMove ].score = programStats.score;
5968         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5969         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5970         ClearProgramStats();
5971         thinkOutput[0] = NULLCHAR;
5972         hiddenThinkOutputState = 0;
5973
5974         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5975
5976         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5977         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5978             int count = 0;
5979
5980             while( count < adjudicateLossPlies ) {
5981                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5982
5983                 if( count & 1 ) {
5984                     score = -score; /* Flip score for winning side */
5985                 }
5986
5987                 if( score > adjudicateLossThreshold ) {
5988                     break;
5989                 }
5990
5991                 count++;
5992             }
5993
5994             if( count >= adjudicateLossPlies ) {
5995                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996
5997                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5998                     "Xboard adjudication", 
5999                     GE_XBOARD );
6000
6001                 return;
6002             }
6003         }
6004
6005         if( gameMode == TwoMachinesPlay ) {
6006           // [HGM] some adjudications useful with buggy engines
6007             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6008           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6009
6010
6011             if( appData.testLegality )
6012             {   /* [HGM] Some more adjudications for obstinate engines */
6013                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6014                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6015                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6016                 static int moveCount = 6;
6017                 ChessMove result;
6018                 char *reason = NULL;
6019
6020                 /* Count what is on board. */
6021                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6022                 {   ChessSquare p = boards[forwardMostMove][i][j];
6023                     int m=i;
6024
6025                     switch((int) p)
6026                     {   /* count B,N,R and other of each side */
6027                         case WhiteKing:
6028                         case BlackKing:
6029                              NrK++; break; // [HGM] atomic: count Kings
6030                         case WhiteKnight:
6031                              NrWN++; break;
6032                         case WhiteBishop:
6033                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6034                              bishopsColor |= 1 << ((i^j)&1);
6035                              NrWB++; break;
6036                         case BlackKnight:
6037                              NrBN++; break;
6038                         case BlackBishop:
6039                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6040                              bishopsColor |= 1 << ((i^j)&1);
6041                              NrBB++; break;
6042                         case WhiteRook:
6043                              NrWR++; break;
6044                         case BlackRook:
6045                              NrBR++; break;
6046                         case WhiteQueen:
6047                              NrWQ++; break;
6048                         case BlackQueen:
6049                              NrBQ++; break;
6050                         case EmptySquare: 
6051                              break;
6052                         case BlackPawn:
6053                              m = 7-i;
6054                         case WhitePawn:
6055                              PawnAdvance += m; NrPawns++;
6056                     }
6057                     NrPieces += (p != EmptySquare);
6058                     NrW += ((int)p < (int)BlackPawn);
6059                     if(gameInfo.variant == VariantXiangqi && 
6060                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6061                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6062                         NrW -= ((int)p < (int)BlackPawn);
6063                     }
6064                 }
6065
6066                 /* Some material-based adjudications that have to be made before stalemate test */
6067                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6068                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6069                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6070                      if(appData.checkMates) {
6071                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6072                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6073                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6074                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6075                          return;
6076                      }
6077                 }
6078
6079                 /* Bare King in Shatranj (loses) or Losers (wins) */
6080                 if( NrW == 1 || NrPieces - NrW == 1) {
6081                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6082                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6083                      if(appData.checkMates) {
6084                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6085                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6086                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6087                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6088                          return;
6089                      }
6090                   } else
6091                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6092                   {    /* bare King */
6093                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6094                         if(appData.checkMates) {
6095                             /* but only adjudicate if adjudication enabled */
6096                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6097                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6098                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6099                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6100                             return;
6101                         }
6102                   }
6103                 } else bare = 1;
6104
6105
6106             // don't wait for engine to announce game end if we can judge ourselves
6107             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6108                                        castlingRights[forwardMostMove]) ) {
6109               case MT_CHECK:
6110                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6111                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6112                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6113                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6114                             checkCnt++;
6115                         if(checkCnt >= 2) {
6116                             reason = "Xboard adjudication: 3rd check";
6117                             epStatus[forwardMostMove] = EP_CHECKMATE;
6118                             break;
6119                         }
6120                     }
6121                 }
6122               case MT_NONE:
6123               default:
6124                 break;
6125               case MT_STALEMATE:
6126               case MT_STAINMATE:
6127                 reason = "Xboard adjudication: Stalemate";
6128                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6129                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6130                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6131                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6132                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6133                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6134                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6135                                                                         EP_CHECKMATE : EP_WINS);
6136                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6137                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6138                 }
6139                 break;
6140               case MT_CHECKMATE:
6141                 reason = "Xboard adjudication: Checkmate";
6142                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6143                 break;
6144             }
6145
6146                 switch(i = epStatus[forwardMostMove]) {
6147                     case EP_STALEMATE:
6148                         result = GameIsDrawn; break;
6149                     case EP_CHECKMATE:
6150                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6151                     case EP_WINS:
6152                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6153                     default:
6154                         result = (ChessMove) 0;
6155                 }
6156                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6157                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6158                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6159                     GameEnds( result, reason, GE_XBOARD );
6160                     return;
6161                 }
6162
6163                 /* Next absolutely insufficient mating material. */
6164                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6165                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6166                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6167                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6168                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6169
6170                      /* always flag draws, for judging claims */
6171                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6172
6173                      if(appData.materialDraws) {
6174                          /* but only adjudicate them if adjudication enabled */
6175                          SendToProgram("force\n", cps->other); // suppress reply
6176                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6177                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6178                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6179                          return;
6180                      }
6181                 }
6182
6183                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6184                 if(NrPieces == 4 && 
6185                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6186                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6187                    || NrWN==2 || NrBN==2     /* KNNK */
6188                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6189                   ) ) {
6190                      if(--moveCount < 0 && appData.trivialDraws)
6191                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6192                           SendToProgram("force\n", cps->other); // suppress reply
6193                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6194                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6195                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6196                           return;
6197                      }
6198                 } else moveCount = 6;
6199             }
6200           }
6201           
6202           if (appData.debugMode) { int i;
6203             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6204                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6205                     appData.drawRepeats);
6206             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6207               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6208             
6209           }
6210
6211                 /* Check for rep-draws */
6212                 count = 0;
6213                 for(k = forwardMostMove-2;
6214                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6215                         epStatus[k] < EP_UNKNOWN &&
6216                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6217                     k-=2)
6218                 {   int rights=0;
6219                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6220                         /* compare castling rights */
6221                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6222                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6223                                 rights++; /* King lost rights, while rook still had them */
6224                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6225                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6226                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6227                                    rights++; /* but at least one rook lost them */
6228                         }
6229                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6230                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6231                                 rights++; 
6232                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6233                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6234                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6235                                    rights++;
6236                         }
6237                         if( rights == 0 && ++count > appData.drawRepeats-2
6238                             && appData.drawRepeats > 1) {
6239                              /* adjudicate after user-specified nr of repeats */
6240                              SendToProgram("force\n", cps->other); // suppress reply
6241                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6242                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6244                                 // [HGM] xiangqi: check for forbidden perpetuals
6245                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6246                                 for(m=forwardMostMove; m>k; m-=2) {
6247                                     if(MateTest(boards[m], PosFlags(m), 
6248                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6249                                         ourPerpetual = 0; // the current mover did not always check
6250                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6251                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6252                                         hisPerpetual = 0; // the opponent did not always check
6253                                 }
6254                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6255                                                                         ourPerpetual, hisPerpetual);
6256                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6257                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6258                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6259                                     return;
6260                                 }
6261                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6262                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6263                                 // Now check for perpetual chases
6264                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6265                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6266                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6267                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6268                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6269                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6270                                         return;
6271                                     }
6272                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6273                                         break; // Abort repetition-checking loop.
6274                                 }
6275                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6276                              }
6277                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6278                              return;
6279                         }
6280                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6281                              epStatus[forwardMostMove] = EP_REP_DRAW;
6282                     }
6283                 }
6284
6285                 /* Now we test for 50-move draws. Determine ply count */
6286                 count = forwardMostMove;
6287                 /* look for last irreversble move */
6288                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6289                     count--;
6290                 /* if we hit starting position, add initial plies */
6291                 if( count == backwardMostMove )
6292                     count -= initialRulePlies;
6293                 count = forwardMostMove - count; 
6294                 if( count >= 100)
6295                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6296                          /* this is used to judge if draw claims are legal */
6297                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6298                          SendToProgram("force\n", cps->other); // suppress reply
6299                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6300                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6301                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6302                          return;
6303                 }
6304
6305                 /* if draw offer is pending, treat it as a draw claim
6306                  * when draw condition present, to allow engines a way to
6307                  * claim draws before making their move to avoid a race
6308                  * condition occurring after their move
6309                  */
6310                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6311                          char *p = NULL;
6312                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6313                              p = "Draw claim: 50-move rule";
6314                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6315                              p = "Draw claim: 3-fold repetition";
6316                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6317                              p = "Draw claim: insufficient mating material";
6318                          if( p != NULL ) {
6319                              SendToProgram("force\n", cps->other); // suppress reply
6320                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6321                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6322                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6323                              return;
6324                          }
6325                 }
6326
6327
6328                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6329                     SendToProgram("force\n", cps->other); // suppress reply
6330                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6331                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6332
6333                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6334
6335                     return;
6336                 }
6337         }
6338
6339         bookHit = NULL;
6340         if (gameMode == TwoMachinesPlay) {
6341             /* [HGM] relaying draw offers moved to after reception of move */
6342             /* and interpreting offer as claim if it brings draw condition */
6343             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6344                 SendToProgram("draw\n", cps->other);
6345             }
6346             if (cps->other->sendTime) {
6347                 SendTimeRemaining(cps->other,
6348                                   cps->other->twoMachinesColor[0] == 'w');
6349             }
6350             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6351             if (firstMove && !bookHit) {
6352                 firstMove = FALSE;
6353                 if (cps->other->useColors) {
6354                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6355                 }
6356                 SendToProgram("go\n", cps->other);
6357             }
6358             cps->other->maybeThinking = TRUE;
6359         }
6360
6361         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362         
6363         if (!pausing && appData.ringBellAfterMoves) {
6364             RingBell();
6365         }
6366
6367         /* 
6368          * Reenable menu items that were disabled while
6369          * machine was thinking
6370          */
6371         if (gameMode != TwoMachinesPlay)
6372             SetUserThinkingEnables();
6373
6374         // [HGM] book: after book hit opponent has received move and is now in force mode
6375         // force the book reply into it, and then fake that it outputted this move by jumping
6376         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6377         if(bookHit) {
6378                 static char bookMove[MSG_SIZ]; // a bit generous?
6379
6380                 strcpy(bookMove, "move ");
6381                 strcat(bookMove, bookHit);
6382                 message = bookMove;
6383                 cps = cps->other;
6384                 programStats.nodes = programStats.depth = programStats.time = 
6385                 programStats.score = programStats.got_only_move = 0;
6386                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6387
6388                 if(cps->lastPing != cps->lastPong) {
6389                     savedMessage = message; // args for deferred call
6390                     savedState = cps;
6391                     ScheduleDelayedEvent(DeferredBookMove, 10);
6392                     return;
6393                 }
6394                 goto FakeBookMove;
6395         }
6396
6397         return;
6398     }
6399
6400     /* Set special modes for chess engines.  Later something general
6401      *  could be added here; for now there is just one kludge feature,
6402      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6403      *  when "xboard" is given as an interactive command.
6404      */
6405     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6406         cps->useSigint = FALSE;
6407         cps->useSigterm = FALSE;
6408     }
6409     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6410       ParseFeatures(message+8, cps);
6411       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6412     }
6413
6414     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6415      * want this, I was asked to put it in, and obliged.
6416      */
6417     if (!strncmp(message, "setboard ", 9)) {
6418         Board initial_position; int i;
6419
6420         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6421
6422         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6423             DisplayError(_("Bad FEN received from engine"), 0);
6424             return ;
6425         } else {
6426            Reset(FALSE, FALSE);
6427            CopyBoard(boards[0], initial_position);
6428            initialRulePlies = FENrulePlies;
6429            epStatus[0] = FENepStatus;
6430            for( i=0; i<nrCastlingRights; i++ )
6431                 castlingRights[0][i] = FENcastlingRights[i];
6432            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6433            else gameMode = MachinePlaysBlack;                 
6434            DrawPosition(FALSE, boards[currentMove]);
6435         }
6436         return;
6437     }
6438
6439     /*
6440      * Look for communication commands
6441      */
6442     if (!strncmp(message, "telluser ", 9)) {
6443         DisplayNote(message + 9);
6444         return;
6445     }
6446     if (!strncmp(message, "tellusererror ", 14)) {
6447         DisplayError(message + 14, 0);
6448         return;
6449     }
6450     if (!strncmp(message, "tellopponent ", 13)) {
6451       if (appData.icsActive) {
6452         if (loggedOn) {
6453           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6454           SendToICS(buf1);
6455         }
6456       } else {
6457         DisplayNote(message + 13);
6458       }
6459       return;
6460     }
6461     if (!strncmp(message, "tellothers ", 11)) {
6462       if (appData.icsActive) {
6463         if (loggedOn) {
6464           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6465           SendToICS(buf1);
6466         }
6467       }
6468       return;
6469     }
6470     if (!strncmp(message, "tellall ", 8)) {
6471       if (appData.icsActive) {
6472         if (loggedOn) {
6473           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6474           SendToICS(buf1);
6475         }
6476       } else {
6477         DisplayNote(message + 8);
6478       }
6479       return;
6480     }
6481     if (strncmp(message, "warning", 7) == 0) {
6482         /* Undocumented feature, use tellusererror in new code */
6483         DisplayError(message, 0);
6484         return;
6485     }
6486     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6487         strcpy(realname, cps->tidy);
6488         strcat(realname, " query");
6489         AskQuestion(realname, buf2, buf1, cps->pr);
6490         return;
6491     }
6492     /* Commands from the engine directly to ICS.  We don't allow these to be 
6493      *  sent until we are logged on. Crafty kibitzes have been known to 
6494      *  interfere with the login process.
6495      */
6496     if (loggedOn) {
6497         if (!strncmp(message, "tellics ", 8)) {
6498             SendToICS(message + 8);
6499             SendToICS("\n");
6500             return;
6501         }
6502         if (!strncmp(message, "tellicsnoalias ", 15)) {
6503             SendToICS(ics_prefix);
6504             SendToICS(message + 15);
6505             SendToICS("\n");
6506             return;
6507         }
6508         /* The following are for backward compatibility only */
6509         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6510             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6511             SendToICS(ics_prefix);
6512             SendToICS(message);
6513             SendToICS("\n");
6514             return;
6515         }
6516     }
6517     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6518         return;
6519     }
6520     /*
6521      * If the move is illegal, cancel it and redraw the board.
6522      * Also deal with other error cases.  Matching is rather loose
6523      * here to accommodate engines written before the spec.
6524      */
6525     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6526         strncmp(message, "Error", 5) == 0) {
6527         if (StrStr(message, "name") || 
6528             StrStr(message, "rating") || StrStr(message, "?") ||
6529             StrStr(message, "result") || StrStr(message, "board") ||
6530             StrStr(message, "bk") || StrStr(message, "computer") ||
6531             StrStr(message, "variant") || StrStr(message, "hint") ||
6532             StrStr(message, "random") || StrStr(message, "depth") ||
6533             StrStr(message, "accepted")) {
6534             return;
6535         }
6536         if (StrStr(message, "protover")) {
6537           /* Program is responding to input, so it's apparently done
6538              initializing, and this error message indicates it is
6539              protocol version 1.  So we don't need to wait any longer
6540              for it to initialize and send feature commands. */
6541           FeatureDone(cps, 1);
6542           cps->protocolVersion = 1;
6543           return;
6544         }
6545         cps->maybeThinking = FALSE;
6546
6547         if (StrStr(message, "draw")) {
6548             /* Program doesn't have "draw" command */
6549             cps->sendDrawOffers = 0;
6550             return;
6551         }
6552         if (cps->sendTime != 1 &&
6553             (StrStr(message, "time") || StrStr(message, "otim"))) {
6554           /* Program apparently doesn't have "time" or "otim" command */
6555           cps->sendTime = 0;
6556           return;
6557         }
6558         if (StrStr(message, "analyze")) {
6559             cps->analysisSupport = FALSE;
6560             cps->analyzing = FALSE;
6561             Reset(FALSE, TRUE);
6562             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6563             DisplayError(buf2, 0);
6564             return;
6565         }
6566         if (StrStr(message, "(no matching move)st")) {
6567           /* Special kludge for GNU Chess 4 only */
6568           cps->stKludge = TRUE;
6569           SendTimeControl(cps, movesPerSession, timeControl,
6570                           timeIncrement, appData.searchDepth,
6571                           searchTime);
6572           return;
6573         }
6574         if (StrStr(message, "(no matching move)sd")) {
6575           /* Special kludge for GNU Chess 4 only */
6576           cps->sdKludge = TRUE;
6577           SendTimeControl(cps, movesPerSession, timeControl,
6578                           timeIncrement, appData.searchDepth,
6579                           searchTime);
6580           return;
6581         }
6582         if (!StrStr(message, "llegal")) {
6583             return;
6584         }
6585         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6586             gameMode == IcsIdle) return;
6587         if (forwardMostMove <= backwardMostMove) return;
6588         if (pausing) PauseEvent();
6589       if(appData.forceIllegal) {
6590             // [HGM] illegal: machine refused move; force position after move into it
6591           SendToProgram("force\n", cps);
6592           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6593                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6594                 // when black is to move, while there might be nothing on a2 or black
6595                 // might already have the move. So send the board as if white has the move.
6596                 // But first we must change the stm of the engine, as it refused the last move
6597                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6598                 if(WhiteOnMove(forwardMostMove)) {
6599                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6600                     SendBoard(cps, forwardMostMove); // kludgeless board
6601                 } else {
6602                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6603                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6604                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6605                 }
6606           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6607             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6608                  gameMode == TwoMachinesPlay)
6609               SendToProgram("go\n", cps);
6610             return;
6611       } else
6612         if (gameMode == PlayFromGameFile) {
6613             /* Stop reading this game file */
6614             gameMode = EditGame;
6615             ModeHighlight();
6616         }
6617         currentMove = --forwardMostMove;
6618         DisplayMove(currentMove-1); /* before DisplayMoveError */
6619         SwitchClocks();
6620         DisplayBothClocks();
6621         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6622                 parseList[currentMove], cps->which);
6623         DisplayMoveError(buf1);
6624         DrawPosition(FALSE, boards[currentMove]);
6625
6626         /* [HGM] illegal-move claim should forfeit game when Xboard */
6627         /* only passes fully legal moves                            */
6628         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6629             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6630                                 "False illegal-move claim", GE_XBOARD );
6631         }
6632         return;
6633     }
6634     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6635         /* Program has a broken "time" command that
6636            outputs a string not ending in newline.
6637            Don't use it. */
6638         cps->sendTime = 0;
6639     }
6640     
6641     /*
6642      * If chess program startup fails, exit with an error message.
6643      * Attempts to recover here are futile.
6644      */
6645     if ((StrStr(message, "unknown host") != NULL)
6646         || (StrStr(message, "No remote directory") != NULL)
6647         || (StrStr(message, "not found") != NULL)
6648         || (StrStr(message, "No such file") != NULL)
6649         || (StrStr(message, "can't alloc") != NULL)
6650         || (StrStr(message, "Permission denied") != NULL)) {
6651
6652         cps->maybeThinking = FALSE;
6653         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6654                 cps->which, cps->program, cps->host, message);
6655         RemoveInputSource(cps->isr);
6656         DisplayFatalError(buf1, 0, 1);
6657         return;
6658     }
6659     
6660     /* 
6661      * Look for hint output
6662      */
6663     if (sscanf(message, "Hint: %s", buf1) == 1) {
6664         if (cps == &first && hintRequested) {
6665             hintRequested = FALSE;
6666             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6667                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6668                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6669                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6670                                     fromY, fromX, toY, toX, promoChar, buf1);
6671                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6672                 DisplayInformation(buf2);
6673             } else {
6674                 /* Hint move could not be parsed!? */
6675               snprintf(buf2, sizeof(buf2),
6676                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6677                         buf1, cps->which);
6678                 DisplayError(buf2, 0);
6679             }
6680         } else {
6681             strcpy(lastHint, buf1);
6682         }
6683         return;
6684     }
6685
6686     /*
6687      * Ignore other messages if game is not in progress
6688      */
6689     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6690         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6691
6692     /*
6693      * look for win, lose, draw, or draw offer
6694      */
6695     if (strncmp(message, "1-0", 3) == 0) {
6696         char *p, *q, *r = "";
6697         p = strchr(message, '{');
6698         if (p) {
6699             q = strchr(p, '}');
6700             if (q) {
6701                 *q = NULLCHAR;
6702                 r = p + 1;
6703             }
6704         }
6705         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6706         return;
6707     } else if (strncmp(message, "0-1", 3) == 0) {
6708         char *p, *q, *r = "";
6709         p = strchr(message, '{');
6710         if (p) {
6711             q = strchr(p, '}');
6712             if (q) {
6713                 *q = NULLCHAR;
6714                 r = p + 1;
6715             }
6716         }
6717         /* Kludge for Arasan 4.1 bug */
6718         if (strcmp(r, "Black resigns") == 0) {
6719             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6720             return;
6721         }
6722         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6723         return;
6724     } else if (strncmp(message, "1/2", 3) == 0) {
6725         char *p, *q, *r = "";
6726         p = strchr(message, '{');
6727         if (p) {
6728             q = strchr(p, '}');
6729             if (q) {
6730                 *q = NULLCHAR;
6731                 r = p + 1;
6732             }
6733         }
6734             
6735         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6736         return;
6737
6738     } else if (strncmp(message, "White resign", 12) == 0) {
6739         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6740         return;
6741     } else if (strncmp(message, "Black resign", 12) == 0) {
6742         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6743         return;
6744     } else if (strncmp(message, "White matches", 13) == 0 ||
6745                strncmp(message, "Black matches", 13) == 0   ) {
6746         /* [HGM] ignore GNUShogi noises */
6747         return;
6748     } else if (strncmp(message, "White", 5) == 0 &&
6749                message[5] != '(' &&
6750                StrStr(message, "Black") == NULL) {
6751         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6752         return;
6753     } else if (strncmp(message, "Black", 5) == 0 &&
6754                message[5] != '(') {
6755         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6756         return;
6757     } else if (strcmp(message, "resign") == 0 ||
6758                strcmp(message, "computer resigns") == 0) {
6759         switch (gameMode) {
6760           case MachinePlaysBlack:
6761           case IcsPlayingBlack:
6762             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6763             break;
6764           case MachinePlaysWhite:
6765           case IcsPlayingWhite:
6766             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6767             break;
6768           case TwoMachinesPlay:
6769             if (cps->twoMachinesColor[0] == 'w')
6770               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6771             else
6772               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6773             break;
6774           default:
6775             /* can't happen */
6776             break;
6777         }
6778         return;
6779     } else if (strncmp(message, "opponent mates", 14) == 0) {
6780         switch (gameMode) {
6781           case MachinePlaysBlack:
6782           case IcsPlayingBlack:
6783             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6784             break;
6785           case MachinePlaysWhite:
6786           case IcsPlayingWhite:
6787             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6788             break;
6789           case TwoMachinesPlay:
6790             if (cps->twoMachinesColor[0] == 'w')
6791               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6792             else
6793               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6794             break;
6795           default:
6796             /* can't happen */
6797             break;
6798         }
6799         return;
6800     } else if (strncmp(message, "computer mates", 14) == 0) {
6801         switch (gameMode) {
6802           case MachinePlaysBlack:
6803           case IcsPlayingBlack:
6804             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6805             break;
6806           case MachinePlaysWhite:
6807           case IcsPlayingWhite:
6808             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6809             break;
6810           case TwoMachinesPlay:
6811             if (cps->twoMachinesColor[0] == 'w')
6812               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6813             else
6814               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6815             break;
6816           default:
6817             /* can't happen */
6818             break;
6819         }
6820         return;
6821     } else if (strncmp(message, "checkmate", 9) == 0) {
6822         if (WhiteOnMove(forwardMostMove)) {
6823             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6824         } else {
6825             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6826         }
6827         return;
6828     } else if (strstr(message, "Draw") != NULL ||
6829                strstr(message, "game is a draw") != NULL) {
6830         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6831         return;
6832     } else if (strstr(message, "offer") != NULL &&
6833                strstr(message, "draw") != NULL) {
6834 #if ZIPPY
6835         if (appData.zippyPlay && first.initDone) {
6836             /* Relay offer to ICS */
6837             SendToICS(ics_prefix);
6838             SendToICS("draw\n");
6839         }
6840 #endif
6841         cps->offeredDraw = 2; /* valid until this engine moves twice */
6842         if (gameMode == TwoMachinesPlay) {
6843             if (cps->other->offeredDraw) {
6844                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6845             /* [HGM] in two-machine mode we delay relaying draw offer      */
6846             /* until after we also have move, to see if it is really claim */
6847             }
6848         } else if (gameMode == MachinePlaysWhite ||
6849                    gameMode == MachinePlaysBlack) {
6850           if (userOfferedDraw) {
6851             DisplayInformation(_("Machine accepts your draw offer"));
6852             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6853           } else {
6854             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6855           }
6856         }
6857     }
6858
6859     
6860     /*
6861      * Look for thinking output
6862      */
6863     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6864           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6865                                 ) {
6866         int plylev, mvleft, mvtot, curscore, time;
6867         char mvname[MOVE_LEN];
6868         u64 nodes; // [DM]
6869         char plyext;
6870         int ignore = FALSE;
6871         int prefixHint = FALSE;
6872         mvname[0] = NULLCHAR;
6873
6874         switch (gameMode) {
6875           case MachinePlaysBlack:
6876           case IcsPlayingBlack:
6877             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6878             break;
6879           case MachinePlaysWhite:
6880           case IcsPlayingWhite:
6881             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6882             break;
6883           case AnalyzeMode:
6884           case AnalyzeFile:
6885             break;
6886           case IcsObserving: /* [DM] icsEngineAnalyze */
6887             if (!appData.icsEngineAnalyze) ignore = TRUE;
6888             break;
6889           case TwoMachinesPlay:
6890             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6891                 ignore = TRUE;
6892             }
6893             break;
6894           default:
6895             ignore = TRUE;
6896             break;
6897         }
6898
6899         if (!ignore) {
6900             buf1[0] = NULLCHAR;
6901             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6902                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6903
6904                 if (plyext != ' ' && plyext != '\t') {
6905                     time *= 100;
6906                 }
6907
6908                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6909                 if( cps->scoreIsAbsolute && 
6910                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6911                 {
6912                     curscore = -curscore;
6913                 }
6914
6915
6916                 programStats.depth = plylev;
6917                 programStats.nodes = nodes;
6918                 programStats.time = time;
6919                 programStats.score = curscore;
6920                 programStats.got_only_move = 0;
6921
6922                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6923                         int ticklen;
6924
6925                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6926                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6927                         if(WhiteOnMove(forwardMostMove)) 
6928                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6929                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6930                 }
6931
6932                 /* Buffer overflow protection */
6933                 if (buf1[0] != NULLCHAR) {
6934                     if (strlen(buf1) >= sizeof(programStats.movelist)
6935                         && appData.debugMode) {
6936                         fprintf(debugFP,
6937                                 "PV is too long; using the first %d bytes.\n",
6938                                 sizeof(programStats.movelist) - 1);
6939                     }
6940
6941                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6942                 } else {
6943                     sprintf(programStats.movelist, " no PV\n");
6944                 }
6945
6946                 if (programStats.seen_stat) {
6947                     programStats.ok_to_send = 1;
6948                 }
6949
6950                 if (strchr(programStats.movelist, '(') != NULL) {
6951                     programStats.line_is_book = 1;
6952                     programStats.nr_moves = 0;
6953                     programStats.moves_left = 0;
6954                 } else {
6955                     programStats.line_is_book = 0;
6956                 }
6957
6958                 SendProgramStatsToFrontend( cps, &programStats );
6959
6960                 /* 
6961                     [AS] Protect the thinkOutput buffer from overflow... this
6962                     is only useful if buf1 hasn't overflowed first!
6963                 */
6964                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6965                         plylev, 
6966                         (gameMode == TwoMachinesPlay ?
6967                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6968                         ((double) curscore) / 100.0,
6969                         prefixHint ? lastHint : "",
6970                         prefixHint ? " " : "" );
6971
6972                 if( buf1[0] != NULLCHAR ) {
6973                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6974
6975                     if( strlen(buf1) > max_len ) {
6976                         if( appData.debugMode) {
6977                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6978                         }
6979                         buf1[max_len+1] = '\0';
6980                     }
6981
6982                     strcat( thinkOutput, buf1 );
6983                 }
6984
6985                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6986                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6987                     DisplayMove(currentMove - 1);
6988                 }
6989                 return;
6990
6991             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6992                 /* crafty (9.25+) says "(only move) <move>"
6993                  * if there is only 1 legal move
6994                  */
6995                 sscanf(p, "(only move) %s", buf1);
6996                 sprintf(thinkOutput, "%s (only move)", buf1);
6997                 sprintf(programStats.movelist, "%s (only move)", buf1);
6998                 programStats.depth = 1;
6999                 programStats.nr_moves = 1;
7000                 programStats.moves_left = 1;
7001                 programStats.nodes = 1;
7002                 programStats.time = 1;
7003                 programStats.got_only_move = 1;
7004
7005                 /* Not really, but we also use this member to
7006                    mean "line isn't going to change" (Crafty
7007                    isn't searching, so stats won't change) */
7008                 programStats.line_is_book = 1;
7009
7010                 SendProgramStatsToFrontend( cps, &programStats );
7011                 
7012                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7013                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7014                     DisplayMove(currentMove - 1);
7015                 }
7016                 return;
7017             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7018                               &time, &nodes, &plylev, &mvleft,
7019                               &mvtot, mvname) >= 5) {
7020                 /* The stat01: line is from Crafty (9.29+) in response
7021                    to the "." command */
7022                 programStats.seen_stat = 1;
7023                 cps->maybeThinking = TRUE;
7024
7025                 if (programStats.got_only_move || !appData.periodicUpdates)
7026                   return;
7027
7028                 programStats.depth = plylev;
7029                 programStats.time = time;
7030                 programStats.nodes = nodes;
7031                 programStats.moves_left = mvleft;
7032                 programStats.nr_moves = mvtot;
7033                 strcpy(programStats.move_name, mvname);
7034                 programStats.ok_to_send = 1;
7035                 programStats.movelist[0] = '\0';
7036
7037                 SendProgramStatsToFrontend( cps, &programStats );
7038
7039                 return;
7040
7041             } else if (strncmp(message,"++",2) == 0) {
7042                 /* Crafty 9.29+ outputs this */
7043                 programStats.got_fail = 2;
7044                 return;
7045
7046             } else if (strncmp(message,"--",2) == 0) {
7047                 /* Crafty 9.29+ outputs this */
7048                 programStats.got_fail = 1;
7049                 return;
7050
7051             } else if (thinkOutput[0] != NULLCHAR &&
7052                        strncmp(message, "    ", 4) == 0) {
7053                 unsigned message_len;
7054
7055                 p = message;
7056                 while (*p && *p == ' ') p++;
7057
7058                 message_len = strlen( p );
7059
7060                 /* [AS] Avoid buffer overflow */
7061                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7062                     strcat(thinkOutput, " ");
7063                     strcat(thinkOutput, p);
7064                 }
7065
7066                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7067                     strcat(programStats.movelist, " ");
7068                     strcat(programStats.movelist, p);
7069                 }
7070
7071                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7072                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7073                     DisplayMove(currentMove - 1);
7074                 }
7075                 return;
7076             }
7077         }
7078         else {
7079             buf1[0] = NULLCHAR;
7080
7081             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7082                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7083             {
7084                 ChessProgramStats cpstats;
7085
7086                 if (plyext != ' ' && plyext != '\t') {
7087                     time *= 100;
7088                 }
7089
7090                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7091                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7092                     curscore = -curscore;
7093                 }
7094
7095                 cpstats.depth = plylev;
7096                 cpstats.nodes = nodes;
7097                 cpstats.time = time;
7098                 cpstats.score = curscore;
7099                 cpstats.got_only_move = 0;
7100                 cpstats.movelist[0] = '\0';
7101
7102                 if (buf1[0] != NULLCHAR) {
7103                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7104                 }
7105
7106                 cpstats.ok_to_send = 0;
7107                 cpstats.line_is_book = 0;
7108                 cpstats.nr_moves = 0;
7109                 cpstats.moves_left = 0;
7110
7111                 SendProgramStatsToFrontend( cps, &cpstats );
7112             }
7113         }
7114     }
7115 }
7116
7117
7118 /* Parse a game score from the character string "game", and
7119    record it as the history of the current game.  The game
7120    score is NOT assumed to start from the standard position. 
7121    The display is not updated in any way.
7122    */
7123 void
7124 ParseGameHistory(game)
7125      char *game;
7126 {
7127     ChessMove moveType;
7128     int fromX, fromY, toX, toY, boardIndex;
7129     char promoChar;
7130     char *p, *q;
7131     char buf[MSG_SIZ];
7132
7133     if (appData.debugMode)
7134       fprintf(debugFP, "Parsing game history: %s\n", game);
7135
7136     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7137     gameInfo.site = StrSave(appData.icsHost);
7138     gameInfo.date = PGNDate();
7139     gameInfo.round = StrSave("-");
7140
7141     /* Parse out names of players */
7142     while (*game == ' ') game++;
7143     p = buf;
7144     while (*game != ' ') *p++ = *game++;
7145     *p = NULLCHAR;
7146     gameInfo.white = StrSave(buf);
7147     while (*game == ' ') game++;
7148     p = buf;
7149     while (*game != ' ' && *game != '\n') *p++ = *game++;
7150     *p = NULLCHAR;
7151     gameInfo.black = StrSave(buf);
7152
7153     /* Parse moves */
7154     boardIndex = blackPlaysFirst ? 1 : 0;
7155     yynewstr(game);
7156     for (;;) {
7157         yyboardindex = boardIndex;
7158         moveType = (ChessMove) yylex();
7159         switch (moveType) {
7160           case IllegalMove:             /* maybe suicide chess, etc. */
7161   if (appData.debugMode) {
7162     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7163     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7164     setbuf(debugFP, NULL);
7165   }
7166           case WhitePromotionChancellor:
7167           case BlackPromotionChancellor:
7168           case WhitePromotionArchbishop:
7169           case BlackPromotionArchbishop:
7170           case WhitePromotionQueen:
7171           case BlackPromotionQueen:
7172           case WhitePromotionRook:
7173           case BlackPromotionRook:
7174           case WhitePromotionBishop:
7175           case BlackPromotionBishop:
7176           case WhitePromotionKnight:
7177           case BlackPromotionKnight:
7178           case WhitePromotionKing:
7179           case BlackPromotionKing:
7180           case NormalMove:
7181           case WhiteCapturesEnPassant:
7182           case BlackCapturesEnPassant:
7183           case WhiteKingSideCastle:
7184           case WhiteQueenSideCastle:
7185           case BlackKingSideCastle:
7186           case BlackQueenSideCastle:
7187           case WhiteKingSideCastleWild:
7188           case WhiteQueenSideCastleWild:
7189           case BlackKingSideCastleWild:
7190           case BlackQueenSideCastleWild:
7191           /* PUSH Fabien */
7192           case WhiteHSideCastleFR:
7193           case WhiteASideCastleFR:
7194           case BlackHSideCastleFR:
7195           case BlackASideCastleFR:
7196           /* POP Fabien */
7197             fromX = currentMoveString[0] - AAA;
7198             fromY = currentMoveString[1] - ONE;
7199             toX = currentMoveString[2] - AAA;
7200             toY = currentMoveString[3] - ONE;
7201             promoChar = currentMoveString[4];
7202             break;
7203           case WhiteDrop:
7204           case BlackDrop:
7205             fromX = moveType == WhiteDrop ?
7206               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7207             (int) CharToPiece(ToLower(currentMoveString[0]));
7208             fromY = DROP_RANK;
7209             toX = currentMoveString[2] - AAA;
7210             toY = currentMoveString[3] - ONE;
7211             promoChar = NULLCHAR;
7212             break;
7213           case AmbiguousMove:
7214             /* bug? */
7215             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7216   if (appData.debugMode) {
7217     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7218     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7219     setbuf(debugFP, NULL);
7220   }
7221             DisplayError(buf, 0);
7222             return;
7223           case ImpossibleMove:
7224             /* bug? */
7225             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7226   if (appData.debugMode) {
7227     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7228     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7229     setbuf(debugFP, NULL);
7230   }
7231             DisplayError(buf, 0);
7232             return;
7233           case (ChessMove) 0:   /* end of file */
7234             if (boardIndex < backwardMostMove) {
7235                 /* Oops, gap.  How did that happen? */
7236                 DisplayError(_("Gap in move list"), 0);
7237                 return;
7238             }
7239             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7240             if (boardIndex > forwardMostMove) {
7241                 forwardMostMove = boardIndex;
7242             }
7243             return;
7244           case ElapsedTime:
7245             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7246                 strcat(parseList[boardIndex-1], " ");
7247                 strcat(parseList[boardIndex-1], yy_text);
7248             }
7249             continue;
7250           case Comment:
7251           case PGNTag:
7252           case NAG:
7253           default:
7254             /* ignore */
7255             continue;
7256           case WhiteWins:
7257           case BlackWins:
7258           case GameIsDrawn:
7259           case GameUnfinished:
7260             if (gameMode == IcsExamining) {
7261                 if (boardIndex < backwardMostMove) {
7262                     /* Oops, gap.  How did that happen? */
7263                     return;
7264                 }
7265                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7266                 return;
7267             }
7268             gameInfo.result = moveType;
7269             p = strchr(yy_text, '{');
7270             if (p == NULL) p = strchr(yy_text, '(');
7271             if (p == NULL) {
7272                 p = yy_text;
7273                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7274             } else {
7275                 q = strchr(p, *p == '{' ? '}' : ')');
7276                 if (q != NULL) *q = NULLCHAR;
7277                 p++;
7278             }
7279             gameInfo.resultDetails = StrSave(p);
7280             continue;
7281         }
7282         if (boardIndex >= forwardMostMove &&
7283             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7284             backwardMostMove = blackPlaysFirst ? 1 : 0;
7285             return;
7286         }
7287         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7288                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7289                                  parseList[boardIndex]);
7290         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7291         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7292         /* currentMoveString is set as a side-effect of yylex */
7293         strcpy(moveList[boardIndex], currentMoveString);
7294         strcat(moveList[boardIndex], "\n");
7295         boardIndex++;
7296         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7297                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7298         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7299                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7300           case MT_NONE:
7301           case MT_STALEMATE:
7302           default:
7303             break;
7304           case MT_CHECK:
7305             if(gameInfo.variant != VariantShogi)
7306                 strcat(parseList[boardIndex - 1], "+");
7307             break;
7308           case MT_CHECKMATE:
7309           case MT_STAINMATE:
7310             strcat(parseList[boardIndex - 1], "#");
7311             break;
7312         }
7313     }
7314 }
7315
7316
7317 /* Apply a move to the given board  */
7318 void
7319 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7320      int fromX, fromY, toX, toY;
7321      int promoChar;
7322      Board board;
7323      char *castling;
7324      char *ep;
7325 {
7326   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7327
7328     /* [HGM] compute & store e.p. status and castling rights for new position */
7329     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7330     { int i;
7331
7332       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7333       oldEP = *ep;
7334       *ep = EP_NONE;
7335
7336       if( board[toY][toX] != EmptySquare ) 
7337            *ep = EP_CAPTURE;  
7338
7339       if( board[fromY][fromX] == WhitePawn ) {
7340            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7341                *ep = EP_PAWN_MOVE;
7342            if( toY-fromY==2) {
7343                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7344                         gameInfo.variant != VariantBerolina || toX < fromX)
7345                       *ep = toX | berolina;
7346                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7347                         gameInfo.variant != VariantBerolina || toX > fromX) 
7348                       *ep = toX;
7349            }
7350       } else 
7351       if( board[fromY][fromX] == BlackPawn ) {
7352            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7353                *ep = EP_PAWN_MOVE; 
7354            if( toY-fromY== -2) {
7355                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7356                         gameInfo.variant != VariantBerolina || toX < fromX)
7357                       *ep = toX | berolina;
7358                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7359                         gameInfo.variant != VariantBerolina || toX > fromX) 
7360                       *ep = toX;
7361            }
7362        }
7363
7364        for(i=0; i<nrCastlingRights; i++) {
7365            if(castling[i] == fromX && castlingRank[i] == fromY ||
7366               castling[i] == toX   && castlingRank[i] == toY   
7367              ) castling[i] = -1; // revoke for moved or captured piece
7368        }
7369
7370     }
7371
7372   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7373   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7374        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7375          
7376   if (fromX == toX && fromY == toY) return;
7377
7378   if (fromY == DROP_RANK) {
7379         /* must be first */
7380         piece = board[toY][toX] = (ChessSquare) fromX;
7381   } else {
7382      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7383      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7384      if(gameInfo.variant == VariantKnightmate)
7385          king += (int) WhiteUnicorn - (int) WhiteKing;
7386
7387     /* Code added by Tord: */
7388     /* FRC castling assumed when king captures friendly rook. */
7389     if (board[fromY][fromX] == WhiteKing &&
7390              board[toY][toX] == WhiteRook) {
7391       board[fromY][fromX] = EmptySquare;
7392       board[toY][toX] = EmptySquare;
7393       if(toX > fromX) {
7394         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7395       } else {
7396         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7397       }
7398     } else if (board[fromY][fromX] == BlackKing &&
7399                board[toY][toX] == BlackRook) {
7400       board[fromY][fromX] = EmptySquare;
7401       board[toY][toX] = EmptySquare;
7402       if(toX > fromX) {
7403         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7404       } else {
7405         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7406       }
7407     /* End of code added by Tord */
7408
7409     } else if (board[fromY][fromX] == king
7410         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7411         && toY == fromY && toX > fromX+1) {
7412         board[fromY][fromX] = EmptySquare;
7413         board[toY][toX] = king;
7414         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7415         board[fromY][BOARD_RGHT-1] = EmptySquare;
7416     } else if (board[fromY][fromX] == king
7417         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7418                && toY == fromY && toX < fromX-1) {
7419         board[fromY][fromX] = EmptySquare;
7420         board[toY][toX] = king;
7421         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7422         board[fromY][BOARD_LEFT] = EmptySquare;
7423     } else if (board[fromY][fromX] == WhitePawn
7424                && toY == BOARD_HEIGHT-1
7425                && gameInfo.variant != VariantXiangqi
7426                ) {
7427         /* white pawn promotion */
7428         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7429         if (board[toY][toX] == EmptySquare) {
7430             board[toY][toX] = WhiteQueen;
7431         }
7432         if(gameInfo.variant==VariantBughouse ||
7433            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7434             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7435         board[fromY][fromX] = EmptySquare;
7436     } else if ((fromY == BOARD_HEIGHT-4)
7437                && (toX != fromX)
7438                && gameInfo.variant != VariantXiangqi
7439                && gameInfo.variant != VariantBerolina
7440                && (board[fromY][fromX] == WhitePawn)
7441                && (board[toY][toX] == EmptySquare)) {
7442         board[fromY][fromX] = EmptySquare;
7443         board[toY][toX] = WhitePawn;
7444         captured = board[toY - 1][toX];
7445         board[toY - 1][toX] = EmptySquare;
7446     } else if ((fromY == BOARD_HEIGHT-4)
7447                && (toX == fromX)
7448                && gameInfo.variant == VariantBerolina
7449                && (board[fromY][fromX] == WhitePawn)
7450                && (board[toY][toX] == EmptySquare)) {
7451         board[fromY][fromX] = EmptySquare;
7452         board[toY][toX] = WhitePawn;
7453         if(oldEP & EP_BEROLIN_A) {
7454                 captured = board[fromY][fromX-1];
7455                 board[fromY][fromX-1] = EmptySquare;
7456         }else{  captured = board[fromY][fromX+1];
7457                 board[fromY][fromX+1] = EmptySquare;
7458         }
7459     } else if (board[fromY][fromX] == king
7460         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7461                && toY == fromY && toX > fromX+1) {
7462         board[fromY][fromX] = EmptySquare;
7463         board[toY][toX] = king;
7464         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7465         board[fromY][BOARD_RGHT-1] = EmptySquare;
7466     } else if (board[fromY][fromX] == king
7467         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7468                && toY == fromY && toX < fromX-1) {
7469         board[fromY][fromX] = EmptySquare;
7470         board[toY][toX] = king;
7471         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7472         board[fromY][BOARD_LEFT] = EmptySquare;
7473     } else if (fromY == 7 && fromX == 3
7474                && board[fromY][fromX] == BlackKing
7475                && toY == 7 && toX == 5) {
7476         board[fromY][fromX] = EmptySquare;
7477         board[toY][toX] = BlackKing;
7478         board[fromY][7] = EmptySquare;
7479         board[toY][4] = BlackRook;
7480     } else if (fromY == 7 && fromX == 3
7481                && board[fromY][fromX] == BlackKing
7482                && toY == 7 && toX == 1) {
7483         board[fromY][fromX] = EmptySquare;
7484         board[toY][toX] = BlackKing;
7485         board[fromY][0] = EmptySquare;
7486         board[toY][2] = BlackRook;
7487     } else if (board[fromY][fromX] == BlackPawn
7488                && toY == 0
7489                && gameInfo.variant != VariantXiangqi
7490                ) {
7491         /* black pawn promotion */
7492         board[0][toX] = CharToPiece(ToLower(promoChar));
7493         if (board[0][toX] == EmptySquare) {
7494             board[0][toX] = BlackQueen;
7495         }
7496         if(gameInfo.variant==VariantBughouse ||
7497            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7498             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7499         board[fromY][fromX] = EmptySquare;
7500     } else if ((fromY == 3)
7501                && (toX != fromX)
7502                && gameInfo.variant != VariantXiangqi
7503                && gameInfo.variant != VariantBerolina
7504                && (board[fromY][fromX] == BlackPawn)
7505                && (board[toY][toX] == EmptySquare)) {
7506         board[fromY][fromX] = EmptySquare;
7507         board[toY][toX] = BlackPawn;
7508         captured = board[toY + 1][toX];
7509         board[toY + 1][toX] = EmptySquare;
7510     } else if ((fromY == 3)
7511                && (toX == fromX)
7512                && gameInfo.variant == VariantBerolina
7513                && (board[fromY][fromX] == BlackPawn)
7514                && (board[toY][toX] == EmptySquare)) {
7515         board[fromY][fromX] = EmptySquare;
7516         board[toY][toX] = BlackPawn;
7517         if(oldEP & EP_BEROLIN_A) {
7518                 captured = board[fromY][fromX-1];
7519                 board[fromY][fromX-1] = EmptySquare;
7520         }else{  captured = board[fromY][fromX+1];
7521                 board[fromY][fromX+1] = EmptySquare;
7522         }
7523     } else {
7524         board[toY][toX] = board[fromY][fromX];
7525         board[fromY][fromX] = EmptySquare;
7526     }
7527
7528     /* [HGM] now we promote for Shogi, if needed */
7529     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7530         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7531   }
7532
7533     if (gameInfo.holdingsWidth != 0) {
7534
7535       /* !!A lot more code needs to be written to support holdings  */
7536       /* [HGM] OK, so I have written it. Holdings are stored in the */
7537       /* penultimate board files, so they are automaticlly stored   */
7538       /* in the game history.                                       */
7539       if (fromY == DROP_RANK) {
7540         /* Delete from holdings, by decreasing count */
7541         /* and erasing image if necessary            */
7542         p = (int) fromX;
7543         if(p < (int) BlackPawn) { /* white drop */
7544              p -= (int)WhitePawn;
7545                  p = PieceToNumber((ChessSquare)p);
7546              if(p >= gameInfo.holdingsSize) p = 0;
7547              if(--board[p][BOARD_WIDTH-2] <= 0)
7548                   board[p][BOARD_WIDTH-1] = EmptySquare;
7549              if((int)board[p][BOARD_WIDTH-2] < 0)
7550                         board[p][BOARD_WIDTH-2] = 0;
7551         } else {                  /* black drop */
7552              p -= (int)BlackPawn;
7553                  p = PieceToNumber((ChessSquare)p);
7554              if(p >= gameInfo.holdingsSize) p = 0;
7555              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7556                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7557              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7558                         board[BOARD_HEIGHT-1-p][1] = 0;
7559         }
7560       }
7561       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7562           && gameInfo.variant != VariantBughouse        ) {
7563         /* [HGM] holdings: Add to holdings, if holdings exist */
7564         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7565                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7566                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7567         }
7568         p = (int) captured;
7569         if (p >= (int) BlackPawn) {
7570           p -= (int)BlackPawn;
7571           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7572                   /* in Shogi restore piece to its original  first */
7573                   captured = (ChessSquare) (DEMOTED captured);
7574                   p = DEMOTED p;
7575           }
7576           p = PieceToNumber((ChessSquare)p);
7577           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7578           board[p][BOARD_WIDTH-2]++;
7579           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7580         } else {
7581           p -= (int)WhitePawn;
7582           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7583                   captured = (ChessSquare) (DEMOTED captured);
7584                   p = DEMOTED p;
7585           }
7586           p = PieceToNumber((ChessSquare)p);
7587           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7588           board[BOARD_HEIGHT-1-p][1]++;
7589           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7590         }
7591       }
7592     } else if (gameInfo.variant == VariantAtomic) {
7593       if (captured != EmptySquare) {
7594         int y, x;
7595         for (y = toY-1; y <= toY+1; y++) {
7596           for (x = toX-1; x <= toX+1; x++) {
7597             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7598                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7599               board[y][x] = EmptySquare;
7600             }
7601           }
7602         }
7603         board[toY][toX] = EmptySquare;
7604       }
7605     }
7606     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7607         /* [HGM] Shogi promotions */
7608         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7609     }
7610
7611     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7612                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7613         // [HGM] superchess: take promotion piece out of holdings
7614         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7615         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7616             if(!--board[k][BOARD_WIDTH-2])
7617                 board[k][BOARD_WIDTH-1] = EmptySquare;
7618         } else {
7619             if(!--board[BOARD_HEIGHT-1-k][1])
7620                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7621         }
7622     }
7623
7624 }
7625
7626 /* Updates forwardMostMove */
7627 void
7628 MakeMove(fromX, fromY, toX, toY, promoChar)
7629      int fromX, fromY, toX, toY;
7630      int promoChar;
7631 {
7632 //    forwardMostMove++; // [HGM] bare: moved downstream
7633
7634     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7635         int timeLeft; static int lastLoadFlag=0; int king, piece;
7636         piece = boards[forwardMostMove][fromY][fromX];
7637         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7638         if(gameInfo.variant == VariantKnightmate)
7639             king += (int) WhiteUnicorn - (int) WhiteKing;
7640         if(forwardMostMove == 0) {
7641             if(blackPlaysFirst) 
7642                 fprintf(serverMoves, "%s;", second.tidy);
7643             fprintf(serverMoves, "%s;", first.tidy);
7644             if(!blackPlaysFirst) 
7645                 fprintf(serverMoves, "%s;", second.tidy);
7646         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7647         lastLoadFlag = loadFlag;
7648         // print base move
7649         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7650         // print castling suffix
7651         if( toY == fromY && piece == king ) {
7652             if(toX-fromX > 1)
7653                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7654             if(fromX-toX >1)
7655                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7656         }
7657         // e.p. suffix
7658         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7659              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7660              boards[forwardMostMove][toY][toX] == EmptySquare
7661              && fromX != toX )
7662                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7663         // promotion suffix
7664         if(promoChar != NULLCHAR)
7665                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7666         if(!loadFlag) {
7667             fprintf(serverMoves, "/%d/%d",
7668                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7669             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7670             else                      timeLeft = blackTimeRemaining/1000;
7671             fprintf(serverMoves, "/%d", timeLeft);
7672         }
7673         fflush(serverMoves);
7674     }
7675
7676     if (forwardMostMove+1 >= MAX_MOVES) {
7677       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7678                         0, 1);
7679       return;
7680     }
7681     if (commentList[forwardMostMove+1] != NULL) {
7682         free(commentList[forwardMostMove+1]);
7683         commentList[forwardMostMove+1] = NULL;
7684     }
7685     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7686     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7687     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7688                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7689     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7690     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7691     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7692     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7693     gameInfo.result = GameUnfinished;
7694     if (gameInfo.resultDetails != NULL) {
7695         free(gameInfo.resultDetails);
7696         gameInfo.resultDetails = NULL;
7697     }
7698     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7699                               moveList[forwardMostMove - 1]);
7700     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7701                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7702                              fromY, fromX, toY, toX, promoChar,
7703                              parseList[forwardMostMove - 1]);
7704     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7705                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7706                             castlingRights[forwardMostMove]) ) {
7707       case MT_NONE:
7708       case MT_STALEMATE:
7709       default:
7710         break;
7711       case MT_CHECK:
7712         if(gameInfo.variant != VariantShogi)
7713             strcat(parseList[forwardMostMove - 1], "+");
7714         break;
7715       case MT_CHECKMATE:
7716       case MT_STAINMATE:
7717         strcat(parseList[forwardMostMove - 1], "#");
7718         break;
7719     }
7720     if (appData.debugMode) {
7721         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7722     }
7723
7724 }
7725
7726 /* Updates currentMove if not pausing */
7727 void
7728 ShowMove(fromX, fromY, toX, toY)
7729 {
7730     int instant = (gameMode == PlayFromGameFile) ?
7731         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7732     if(appData.noGUI) return;
7733     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7734         if (!instant) {
7735             if (forwardMostMove == currentMove + 1) {
7736                 AnimateMove(boards[forwardMostMove - 1],
7737                             fromX, fromY, toX, toY);
7738             }
7739             if (appData.highlightLastMove) {
7740                 SetHighlights(fromX, fromY, toX, toY);
7741             }
7742         }
7743         currentMove = forwardMostMove;
7744     }
7745
7746     if (instant) return;
7747
7748     DisplayMove(currentMove - 1);
7749     DrawPosition(FALSE, boards[currentMove]);
7750     DisplayBothClocks();
7751     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7752 }
7753
7754 void SendEgtPath(ChessProgramState *cps)
7755 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7756         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7757
7758         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7759
7760         while(*p) {
7761             char c, *q = name+1, *r, *s;
7762
7763             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7764             while(*p && *p != ',') *q++ = *p++;
7765             *q++ = ':'; *q = 0;
7766             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7767                 strcmp(name, ",nalimov:") == 0 ) {
7768                 // take nalimov path from the menu-changeable option first, if it is defined
7769                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7770                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7771             } else
7772             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7773                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7774                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7775                 s = r = StrStr(s, ":") + 1; // beginning of path info
7776                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7777                 c = *r; *r = 0;             // temporarily null-terminate path info
7778                     *--q = 0;               // strip of trailig ':' from name
7779                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7780                 *r = c;
7781                 SendToProgram(buf,cps);     // send egtbpath command for this format
7782             }
7783             if(*p == ',') p++; // read away comma to position for next format name
7784         }
7785 }
7786
7787 void
7788 InitChessProgram(cps, setup)
7789      ChessProgramState *cps;
7790      int setup; /* [HGM] needed to setup FRC opening position */
7791 {
7792     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7793     if (appData.noChessProgram) return;
7794     hintRequested = FALSE;
7795     bookRequested = FALSE;
7796
7797     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7798     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7799     if(cps->memSize) { /* [HGM] memory */
7800         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7801         SendToProgram(buf, cps);
7802     }
7803     SendEgtPath(cps); /* [HGM] EGT */
7804     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7805         sprintf(buf, "cores %d\n", appData.smpCores);
7806         SendToProgram(buf, cps);
7807     }
7808
7809     SendToProgram(cps->initString, cps);
7810     if (gameInfo.variant != VariantNormal &&
7811         gameInfo.variant != VariantLoadable
7812         /* [HGM] also send variant if board size non-standard */
7813         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7814                                             ) {
7815       char *v = VariantName(gameInfo.variant);
7816       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7817         /* [HGM] in protocol 1 we have to assume all variants valid */
7818         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7819         DisplayFatalError(buf, 0, 1);
7820         return;
7821       }
7822
7823       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7824       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7825       if( gameInfo.variant == VariantXiangqi )
7826            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7827       if( gameInfo.variant == VariantShogi )
7828            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7829       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7830            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7831       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7832                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7833            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7834       if( gameInfo.variant == VariantCourier )
7835            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7836       if( gameInfo.variant == VariantSuper )
7837            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7838       if( gameInfo.variant == VariantGreat )
7839            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7840
7841       if(overruled) {
7842            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7843                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7844            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7845            if(StrStr(cps->variants, b) == NULL) { 
7846                // specific sized variant not known, check if general sizing allowed
7847                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7848                    if(StrStr(cps->variants, "boardsize") == NULL) {
7849                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7850                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7851                        DisplayFatalError(buf, 0, 1);
7852                        return;
7853                    }
7854                    /* [HGM] here we really should compare with the maximum supported board size */
7855                }
7856            }
7857       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7858       sprintf(buf, "variant %s\n", b);
7859       SendToProgram(buf, cps);
7860     }
7861     currentlyInitializedVariant = gameInfo.variant;
7862
7863     /* [HGM] send opening position in FRC to first engine */
7864     if(setup) {
7865           SendToProgram("force\n", cps);
7866           SendBoard(cps, 0);
7867           /* engine is now in force mode! Set flag to wake it up after first move. */
7868           setboardSpoiledMachineBlack = 1;
7869     }
7870
7871     if (cps->sendICS) {
7872       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7873       SendToProgram(buf, cps);
7874     }
7875     cps->maybeThinking = FALSE;
7876     cps->offeredDraw = 0;
7877     if (!appData.icsActive) {
7878         SendTimeControl(cps, movesPerSession, timeControl,
7879                         timeIncrement, appData.searchDepth,
7880                         searchTime);
7881     }
7882     if (appData.showThinking 
7883         // [HGM] thinking: four options require thinking output to be sent
7884         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7885                                 ) {
7886         SendToProgram("post\n", cps);
7887     }
7888     SendToProgram("hard\n", cps);
7889     if (!appData.ponderNextMove) {
7890         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7891            it without being sure what state we are in first.  "hard"
7892            is not a toggle, so that one is OK.
7893          */
7894         SendToProgram("easy\n", cps);
7895     }
7896     if (cps->usePing) {
7897       sprintf(buf, "ping %d\n", ++cps->lastPing);
7898       SendToProgram(buf, cps);
7899     }
7900     cps->initDone = TRUE;
7901 }   
7902
7903
7904 void
7905 StartChessProgram(cps)
7906      ChessProgramState *cps;
7907 {
7908     char buf[MSG_SIZ];
7909     int err;
7910
7911     if (appData.noChessProgram) return;
7912     cps->initDone = FALSE;
7913
7914     if (strcmp(cps->host, "localhost") == 0) {
7915         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7916     } else if (*appData.remoteShell == NULLCHAR) {
7917         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7918     } else {
7919         if (*appData.remoteUser == NULLCHAR) {
7920           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7921                     cps->program);
7922         } else {
7923           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7924                     cps->host, appData.remoteUser, cps->program);
7925         }
7926         err = StartChildProcess(buf, "", &cps->pr);
7927     }
7928     
7929     if (err != 0) {
7930         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7931         DisplayFatalError(buf, err, 1);
7932         cps->pr = NoProc;
7933         cps->isr = NULL;
7934         return;
7935     }
7936     
7937     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7938     if (cps->protocolVersion > 1) {
7939       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7940       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7941       cps->comboCnt = 0;  //                and values of combo boxes
7942       SendToProgram(buf, cps);
7943     } else {
7944       SendToProgram("xboard\n", cps);
7945     }
7946 }
7947
7948
7949 void
7950 TwoMachinesEventIfReady P((void))
7951 {
7952   if (first.lastPing != first.lastPong) {
7953     DisplayMessage("", _("Waiting for first chess program"));
7954     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7955     return;
7956   }
7957   if (second.lastPing != second.lastPong) {
7958     DisplayMessage("", _("Waiting for second chess program"));
7959     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7960     return;
7961   }
7962   ThawUI();
7963   TwoMachinesEvent();
7964 }
7965
7966 void
7967 NextMatchGame P((void))
7968 {
7969     int index; /* [HGM] autoinc: step lod index during match */
7970     Reset(FALSE, TRUE);
7971     if (*appData.loadGameFile != NULLCHAR) {
7972         index = appData.loadGameIndex;
7973         if(index < 0) { // [HGM] autoinc
7974             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7975             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7976         } 
7977         LoadGameFromFile(appData.loadGameFile,
7978                          index,
7979                          appData.loadGameFile, FALSE);
7980     } else if (*appData.loadPositionFile != NULLCHAR) {
7981         index = appData.loadPositionIndex;
7982         if(index < 0) { // [HGM] autoinc
7983             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7984             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7985         } 
7986         LoadPositionFromFile(appData.loadPositionFile,
7987                              index,
7988                              appData.loadPositionFile);
7989     }
7990     TwoMachinesEventIfReady();
7991 }
7992
7993 void UserAdjudicationEvent( int result )
7994 {
7995     ChessMove gameResult = GameIsDrawn;
7996
7997     if( result > 0 ) {
7998         gameResult = WhiteWins;
7999     }
8000     else if( result < 0 ) {
8001         gameResult = BlackWins;
8002     }
8003
8004     if( gameMode == TwoMachinesPlay ) {
8005         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8006     }
8007 }
8008
8009
8010 // [HGM] save: calculate checksum of game to make games easily identifiable
8011 int StringCheckSum(char *s)
8012 {
8013         int i = 0;
8014         if(s==NULL) return 0;
8015         while(*s) i = i*259 + *s++;
8016         return i;
8017 }
8018
8019 int GameCheckSum()
8020 {
8021         int i, sum=0;
8022         for(i=backwardMostMove; i<forwardMostMove; i++) {
8023                 sum += pvInfoList[i].depth;
8024                 sum += StringCheckSum(parseList[i]);
8025                 sum += StringCheckSum(commentList[i]);
8026                 sum *= 261;
8027         }
8028         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8029         return sum + StringCheckSum(commentList[i]);
8030 } // end of save patch
8031
8032 void
8033 GameEnds(result, resultDetails, whosays)
8034      ChessMove result;
8035      char *resultDetails;
8036      int whosays;
8037 {
8038     GameMode nextGameMode;
8039     int isIcsGame;
8040     char buf[MSG_SIZ];
8041
8042     if(endingGame) return; /* [HGM] crash: forbid recursion */
8043     endingGame = 1;
8044
8045     if (appData.debugMode) {
8046       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8047               result, resultDetails ? resultDetails : "(null)", whosays);
8048     }
8049
8050     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8051         /* If we are playing on ICS, the server decides when the
8052            game is over, but the engine can offer to draw, claim 
8053            a draw, or resign. 
8054          */
8055 #if ZIPPY
8056         if (appData.zippyPlay && first.initDone) {
8057             if (result == GameIsDrawn) {
8058                 /* In case draw still needs to be claimed */
8059                 SendToICS(ics_prefix);
8060                 SendToICS("draw\n");
8061             } else if (StrCaseStr(resultDetails, "resign")) {
8062                 SendToICS(ics_prefix);
8063                 SendToICS("resign\n");
8064             }
8065         }
8066 #endif
8067         endingGame = 0; /* [HGM] crash */
8068         return;
8069     }
8070
8071     /* If we're loading the game from a file, stop */
8072     if (whosays == GE_FILE) {
8073       (void) StopLoadGameTimer();
8074       gameFileFP = NULL;
8075     }
8076
8077     /* Cancel draw offers */
8078     first.offeredDraw = second.offeredDraw = 0;
8079
8080     /* If this is an ICS game, only ICS can really say it's done;
8081        if not, anyone can. */
8082     isIcsGame = (gameMode == IcsPlayingWhite || 
8083                  gameMode == IcsPlayingBlack || 
8084                  gameMode == IcsObserving    || 
8085                  gameMode == IcsExamining);
8086
8087     if (!isIcsGame || whosays == GE_ICS) {
8088         /* OK -- not an ICS game, or ICS said it was done */
8089         StopClocks();
8090         if (!isIcsGame && !appData.noChessProgram) 
8091           SetUserThinkingEnables();
8092     
8093         /* [HGM] if a machine claims the game end we verify this claim */
8094         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8095             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8096                 char claimer;
8097                 ChessMove trueResult = (ChessMove) -1;
8098
8099                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8100                                             first.twoMachinesColor[0] :
8101                                             second.twoMachinesColor[0] ;
8102
8103                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8104                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8105                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8106                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8107                 } else
8108                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8109                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8110                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8111                 } else
8112                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8113                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8114                 }
8115
8116                 // now verify win claims, but not in drop games, as we don't understand those yet
8117                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8118                                                  || gameInfo.variant == VariantGreat) &&
8119                     (result == WhiteWins && claimer == 'w' ||
8120                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8121                       if (appData.debugMode) {
8122                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8123                                 result, epStatus[forwardMostMove], forwardMostMove);
8124                       }
8125                       if(result != trueResult) {
8126                               sprintf(buf, "False win claim: '%s'", resultDetails);
8127                               result = claimer == 'w' ? BlackWins : WhiteWins;
8128                               resultDetails = buf;
8129                       }
8130                 } else
8131                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8132                     && (forwardMostMove <= backwardMostMove ||
8133                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8134                         (claimer=='b')==(forwardMostMove&1))
8135                                                                                   ) {
8136                       /* [HGM] verify: draws that were not flagged are false claims */
8137                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8138                       result = claimer == 'w' ? BlackWins : WhiteWins;
8139                       resultDetails = buf;
8140                 }
8141                 /* (Claiming a loss is accepted no questions asked!) */
8142             }
8143             /* [HGM] bare: don't allow bare King to win */
8144             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8145                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8146                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8147                && result != GameIsDrawn)
8148             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8149                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8150                         int p = (int)boards[forwardMostMove][i][j] - color;
8151                         if(p >= 0 && p <= (int)WhiteKing) k++;
8152                 }
8153                 if (appData.debugMode) {
8154                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8155                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8156                 }
8157                 if(k <= 1) {
8158                         result = GameIsDrawn;
8159                         sprintf(buf, "%s but bare king", resultDetails);
8160                         resultDetails = buf;
8161                 }
8162             }
8163         }
8164
8165
8166         if(serverMoves != NULL && !loadFlag) { char c = '=';
8167             if(result==WhiteWins) c = '+';
8168             if(result==BlackWins) c = '-';
8169             if(resultDetails != NULL)
8170                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8171         }
8172         if (resultDetails != NULL) {
8173             gameInfo.result = result;
8174             gameInfo.resultDetails = StrSave(resultDetails);
8175
8176             /* display last move only if game was not loaded from file */
8177             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8178                 DisplayMove(currentMove - 1);
8179     
8180             if (forwardMostMove != 0) {
8181                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8182                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8183                                                                 ) {
8184                     if (*appData.saveGameFile != NULLCHAR) {
8185                         SaveGameToFile(appData.saveGameFile, TRUE);
8186                     } else if (appData.autoSaveGames) {
8187                         AutoSaveGame();
8188                     }
8189                     if (*appData.savePositionFile != NULLCHAR) {
8190                         SavePositionToFile(appData.savePositionFile);
8191                     }
8192                 }
8193             }
8194
8195             /* Tell program how game ended in case it is learning */
8196             /* [HGM] Moved this to after saving the PGN, just in case */
8197             /* engine died and we got here through time loss. In that */
8198             /* case we will get a fatal error writing the pipe, which */
8199             /* would otherwise lose us the PGN.                       */
8200             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8201             /* output during GameEnds should never be fatal anymore   */
8202             if (gameMode == MachinePlaysWhite ||
8203                 gameMode == MachinePlaysBlack ||
8204                 gameMode == TwoMachinesPlay ||
8205                 gameMode == IcsPlayingWhite ||
8206                 gameMode == IcsPlayingBlack ||
8207                 gameMode == BeginningOfGame) {
8208                 char buf[MSG_SIZ];
8209                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8210                         resultDetails);
8211                 if (first.pr != NoProc) {
8212                     SendToProgram(buf, &first);
8213                 }
8214                 if (second.pr != NoProc &&
8215                     gameMode == TwoMachinesPlay) {
8216                     SendToProgram(buf, &second);
8217                 }
8218             }
8219         }
8220
8221         if (appData.icsActive) {
8222             if (appData.quietPlay &&
8223                 (gameMode == IcsPlayingWhite ||
8224                  gameMode == IcsPlayingBlack)) {
8225                 SendToICS(ics_prefix);
8226                 SendToICS("set shout 1\n");
8227             }
8228             nextGameMode = IcsIdle;
8229             ics_user_moved = FALSE;
8230             /* clean up premove.  It's ugly when the game has ended and the
8231              * premove highlights are still on the board.
8232              */
8233             if (gotPremove) {
8234               gotPremove = FALSE;
8235               ClearPremoveHighlights();
8236               DrawPosition(FALSE, boards[currentMove]);
8237             }
8238             if (whosays == GE_ICS) {
8239                 switch (result) {
8240                 case WhiteWins:
8241                     if (gameMode == IcsPlayingWhite)
8242                         PlayIcsWinSound();
8243                     else if(gameMode == IcsPlayingBlack)
8244                         PlayIcsLossSound();
8245                     break;
8246                 case BlackWins:
8247                     if (gameMode == IcsPlayingBlack)
8248                         PlayIcsWinSound();
8249                     else if(gameMode == IcsPlayingWhite)
8250                         PlayIcsLossSound();
8251                     break;
8252                 case GameIsDrawn:
8253                     PlayIcsDrawSound();
8254                     break;
8255                 default:
8256                     PlayIcsUnfinishedSound();
8257                 }
8258             }
8259         } else if (gameMode == EditGame ||
8260                    gameMode == PlayFromGameFile || 
8261                    gameMode == AnalyzeMode || 
8262                    gameMode == AnalyzeFile) {
8263             nextGameMode = gameMode;
8264         } else {
8265             nextGameMode = EndOfGame;
8266         }
8267         pausing = FALSE;
8268         ModeHighlight();
8269     } else {
8270         nextGameMode = gameMode;
8271     }
8272
8273     if (appData.noChessProgram) {
8274         gameMode = nextGameMode;
8275         ModeHighlight();
8276         endingGame = 0; /* [HGM] crash */
8277         return;
8278     }
8279
8280     if (first.reuse) {
8281         /* Put first chess program into idle state */
8282         if (first.pr != NoProc &&
8283             (gameMode == MachinePlaysWhite ||
8284              gameMode == MachinePlaysBlack ||
8285              gameMode == TwoMachinesPlay ||
8286              gameMode == IcsPlayingWhite ||
8287              gameMode == IcsPlayingBlack ||
8288              gameMode == BeginningOfGame)) {
8289             SendToProgram("force\n", &first);
8290             if (first.usePing) {
8291               char buf[MSG_SIZ];
8292               sprintf(buf, "ping %d\n", ++first.lastPing);
8293               SendToProgram(buf, &first);
8294             }
8295         }
8296     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8297         /* Kill off first chess program */
8298         if (first.isr != NULL)
8299           RemoveInputSource(first.isr);
8300         first.isr = NULL;
8301     
8302         if (first.pr != NoProc) {
8303             ExitAnalyzeMode();
8304             DoSleep( appData.delayBeforeQuit );
8305             SendToProgram("quit\n", &first);
8306             DoSleep( appData.delayAfterQuit );
8307             DestroyChildProcess(first.pr, first.useSigterm);
8308         }
8309         first.pr = NoProc;
8310     }
8311     if (second.reuse) {
8312         /* Put second chess program into idle state */
8313         if (second.pr != NoProc &&
8314             gameMode == TwoMachinesPlay) {
8315             SendToProgram("force\n", &second);
8316             if (second.usePing) {
8317               char buf[MSG_SIZ];
8318               sprintf(buf, "ping %d\n", ++second.lastPing);
8319               SendToProgram(buf, &second);
8320             }
8321         }
8322     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8323         /* Kill off second chess program */
8324         if (second.isr != NULL)
8325           RemoveInputSource(second.isr);
8326         second.isr = NULL;
8327     
8328         if (second.pr != NoProc) {
8329             DoSleep( appData.delayBeforeQuit );
8330             SendToProgram("quit\n", &second);
8331             DoSleep( appData.delayAfterQuit );
8332             DestroyChildProcess(second.pr, second.useSigterm);
8333         }
8334         second.pr = NoProc;
8335     }
8336
8337     if (matchMode && gameMode == TwoMachinesPlay) {
8338         switch (result) {
8339         case WhiteWins:
8340           if (first.twoMachinesColor[0] == 'w') {
8341             first.matchWins++;
8342           } else {
8343             second.matchWins++;
8344           }
8345           break;
8346         case BlackWins:
8347           if (first.twoMachinesColor[0] == 'b') {
8348             first.matchWins++;
8349           } else {
8350             second.matchWins++;
8351           }
8352           break;
8353         default:
8354           break;
8355         }
8356         if (matchGame < appData.matchGames) {
8357             char *tmp;
8358             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8359                 tmp = first.twoMachinesColor;
8360                 first.twoMachinesColor = second.twoMachinesColor;
8361                 second.twoMachinesColor = tmp;
8362             }
8363             gameMode = nextGameMode;
8364             matchGame++;
8365             if(appData.matchPause>10000 || appData.matchPause<10)
8366                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8367             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8368             endingGame = 0; /* [HGM] crash */
8369             return;
8370         } else {
8371             char buf[MSG_SIZ];
8372             gameMode = nextGameMode;
8373             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8374                     first.tidy, second.tidy,
8375                     first.matchWins, second.matchWins,
8376                     appData.matchGames - (first.matchWins + second.matchWins));
8377             DisplayFatalError(buf, 0, 0);
8378         }
8379     }
8380     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8381         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8382       ExitAnalyzeMode();
8383     gameMode = nextGameMode;
8384     ModeHighlight();
8385     endingGame = 0;  /* [HGM] crash */
8386 }
8387
8388 /* Assumes program was just initialized (initString sent).
8389    Leaves program in force mode. */
8390 void
8391 FeedMovesToProgram(cps, upto) 
8392      ChessProgramState *cps;
8393      int upto;
8394 {
8395     int i;
8396     
8397     if (appData.debugMode)
8398       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8399               startedFromSetupPosition ? "position and " : "",
8400               backwardMostMove, upto, cps->which);
8401     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8402         // [HGM] variantswitch: make engine aware of new variant
8403         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8404                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8405         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8406         SendToProgram(buf, cps);
8407         currentlyInitializedVariant = gameInfo.variant;
8408     }
8409     SendToProgram("force\n", cps);
8410     if (startedFromSetupPosition) {
8411         SendBoard(cps, backwardMostMove);
8412     if (appData.debugMode) {
8413         fprintf(debugFP, "feedMoves\n");
8414     }
8415     }
8416     for (i = backwardMostMove; i < upto; i++) {
8417         SendMoveToProgram(i, cps);
8418     }
8419 }
8420
8421
8422 void
8423 ResurrectChessProgram()
8424 {
8425      /* The chess program may have exited.
8426         If so, restart it and feed it all the moves made so far. */
8427
8428     if (appData.noChessProgram || first.pr != NoProc) return;
8429     
8430     StartChessProgram(&first);
8431     InitChessProgram(&first, FALSE);
8432     FeedMovesToProgram(&first, currentMove);
8433
8434     if (!first.sendTime) {
8435         /* can't tell gnuchess what its clock should read,
8436            so we bow to its notion. */
8437         ResetClocks();
8438         timeRemaining[0][currentMove] = whiteTimeRemaining;
8439         timeRemaining[1][currentMove] = blackTimeRemaining;
8440     }
8441
8442     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8443                 appData.icsEngineAnalyze) && first.analysisSupport) {
8444       SendToProgram("analyze\n", &first);
8445       first.analyzing = TRUE;
8446     }
8447 }
8448
8449 /*
8450  * Button procedures
8451  */
8452 void
8453 Reset(redraw, init)
8454      int redraw, init;
8455 {
8456     int i;
8457
8458     if (appData.debugMode) {
8459         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8460                 redraw, init, gameMode);
8461     }
8462     pausing = pauseExamInvalid = FALSE;
8463     startedFromSetupPosition = blackPlaysFirst = FALSE;
8464     firstMove = TRUE;
8465     whiteFlag = blackFlag = FALSE;
8466     userOfferedDraw = FALSE;
8467     hintRequested = bookRequested = FALSE;
8468     first.maybeThinking = FALSE;
8469     second.maybeThinking = FALSE;
8470     first.bookSuspend = FALSE; // [HGM] book
8471     second.bookSuspend = FALSE;
8472     thinkOutput[0] = NULLCHAR;
8473     lastHint[0] = NULLCHAR;
8474     ClearGameInfo(&gameInfo);
8475     gameInfo.variant = StringToVariant(appData.variant);
8476     ics_user_moved = ics_clock_paused = FALSE;
8477     ics_getting_history = H_FALSE;
8478     ics_gamenum = -1;
8479     white_holding[0] = black_holding[0] = NULLCHAR;
8480     ClearProgramStats();
8481     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8482     
8483     ResetFrontEnd();
8484     ClearHighlights();
8485     flipView = appData.flipView;
8486     ClearPremoveHighlights();
8487     gotPremove = FALSE;
8488     alarmSounded = FALSE;
8489
8490     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8491     if(appData.serverMovesName != NULL) {
8492         /* [HGM] prepare to make moves file for broadcasting */
8493         clock_t t = clock();
8494         if(serverMoves != NULL) fclose(serverMoves);
8495         serverMoves = fopen(appData.serverMovesName, "r");
8496         if(serverMoves != NULL) {
8497             fclose(serverMoves);
8498             /* delay 15 sec before overwriting, so all clients can see end */
8499             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8500         }
8501         serverMoves = fopen(appData.serverMovesName, "w");
8502     }
8503
8504     ExitAnalyzeMode();
8505     gameMode = BeginningOfGame;
8506     ModeHighlight();
8507     if(appData.icsActive) gameInfo.variant = VariantNormal;
8508     currentMove = forwardMostMove = backwardMostMove = 0;
8509     InitPosition(redraw);
8510     for (i = 0; i < MAX_MOVES; i++) {
8511         if (commentList[i] != NULL) {
8512             free(commentList[i]);
8513             commentList[i] = NULL;
8514         }
8515     }
8516     ResetClocks();
8517     timeRemaining[0][0] = whiteTimeRemaining;
8518     timeRemaining[1][0] = blackTimeRemaining;
8519     if (first.pr == NULL) {
8520         StartChessProgram(&first);
8521     }
8522     if (init) {
8523             InitChessProgram(&first, startedFromSetupPosition);
8524     }
8525     DisplayTitle("");
8526     DisplayMessage("", "");
8527     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8528     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8529 }
8530
8531 void
8532 AutoPlayGameLoop()
8533 {
8534     for (;;) {
8535         if (!AutoPlayOneMove())
8536           return;
8537         if (matchMode || appData.timeDelay == 0)
8538           continue;
8539         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8540           return;
8541         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8542         break;
8543     }
8544 }
8545
8546
8547 int
8548 AutoPlayOneMove()
8549 {
8550     int fromX, fromY, toX, toY;
8551
8552     if (appData.debugMode) {
8553       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8554     }
8555
8556     if (gameMode != PlayFromGameFile)
8557       return FALSE;
8558
8559     if (currentMove >= forwardMostMove) {
8560       gameMode = EditGame;
8561       ModeHighlight();
8562
8563       /* [AS] Clear current move marker at the end of a game */
8564       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8565
8566       return FALSE;
8567     }
8568     
8569     toX = moveList[currentMove][2] - AAA;
8570     toY = moveList[currentMove][3] - ONE;
8571
8572     if (moveList[currentMove][1] == '@') {
8573         if (appData.highlightLastMove) {
8574             SetHighlights(-1, -1, toX, toY);
8575         }
8576     } else {
8577         fromX = moveList[currentMove][0] - AAA;
8578         fromY = moveList[currentMove][1] - ONE;
8579
8580         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8581
8582         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8583
8584         if (appData.highlightLastMove) {
8585             SetHighlights(fromX, fromY, toX, toY);
8586         }
8587     }
8588     DisplayMove(currentMove);
8589     SendMoveToProgram(currentMove++, &first);
8590     DisplayBothClocks();
8591     DrawPosition(FALSE, boards[currentMove]);
8592     // [HGM] PV info: always display, routine tests if empty
8593     DisplayComment(currentMove - 1, commentList[currentMove]);
8594     return TRUE;
8595 }
8596
8597
8598 int
8599 LoadGameOneMove(readAhead)
8600      ChessMove readAhead;
8601 {
8602     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8603     char promoChar = NULLCHAR;
8604     ChessMove moveType;
8605     char move[MSG_SIZ];
8606     char *p, *q;
8607     
8608     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8609         gameMode != AnalyzeMode && gameMode != Training) {
8610         gameFileFP = NULL;
8611         return FALSE;
8612     }
8613     
8614     yyboardindex = forwardMostMove;
8615     if (readAhead != (ChessMove)0) {
8616       moveType = readAhead;
8617     } else {
8618       if (gameFileFP == NULL)
8619           return FALSE;
8620       moveType = (ChessMove) yylex();
8621     }
8622     
8623     done = FALSE;
8624     switch (moveType) {
8625       case Comment:
8626         if (appData.debugMode) 
8627           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8628         p = yy_text;
8629         if (*p == '{' || *p == '[' || *p == '(') {
8630             p[strlen(p) - 1] = NULLCHAR;
8631             p++;
8632         }
8633
8634         /* append the comment but don't display it */
8635         while (*p == '\n') p++;
8636         AppendComment(currentMove, p);
8637         return TRUE;
8638
8639       case WhiteCapturesEnPassant:
8640       case BlackCapturesEnPassant:
8641       case WhitePromotionChancellor:
8642       case BlackPromotionChancellor:
8643       case WhitePromotionArchbishop:
8644       case BlackPromotionArchbishop:
8645       case WhitePromotionCentaur:
8646       case BlackPromotionCentaur:
8647       case WhitePromotionQueen:
8648       case BlackPromotionQueen:
8649       case WhitePromotionRook:
8650       case BlackPromotionRook:
8651       case WhitePromotionBishop:
8652       case BlackPromotionBishop:
8653       case WhitePromotionKnight:
8654       case BlackPromotionKnight:
8655       case WhitePromotionKing:
8656       case BlackPromotionKing:
8657       case NormalMove:
8658       case WhiteKingSideCastle:
8659       case WhiteQueenSideCastle:
8660       case BlackKingSideCastle:
8661       case BlackQueenSideCastle:
8662       case WhiteKingSideCastleWild:
8663       case WhiteQueenSideCastleWild:
8664       case BlackKingSideCastleWild:
8665       case BlackQueenSideCastleWild:
8666       /* PUSH Fabien */
8667       case WhiteHSideCastleFR:
8668       case WhiteASideCastleFR:
8669       case BlackHSideCastleFR:
8670       case BlackASideCastleFR:
8671       /* POP Fabien */
8672         if (appData.debugMode)
8673           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8674         fromX = currentMoveString[0] - AAA;
8675         fromY = currentMoveString[1] - ONE;
8676         toX = currentMoveString[2] - AAA;
8677         toY = currentMoveString[3] - ONE;
8678         promoChar = currentMoveString[4];
8679         break;
8680
8681       case WhiteDrop:
8682       case BlackDrop:
8683         if (appData.debugMode)
8684           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8685         fromX = moveType == WhiteDrop ?
8686           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8687         (int) CharToPiece(ToLower(currentMoveString[0]));
8688         fromY = DROP_RANK;
8689         toX = currentMoveString[2] - AAA;
8690         toY = currentMoveString[3] - ONE;
8691         break;
8692
8693       case WhiteWins:
8694       case BlackWins:
8695       case GameIsDrawn:
8696       case GameUnfinished:
8697         if (appData.debugMode)
8698           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8699         p = strchr(yy_text, '{');
8700         if (p == NULL) p = strchr(yy_text, '(');
8701         if (p == NULL) {
8702             p = yy_text;
8703             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8704         } else {
8705             q = strchr(p, *p == '{' ? '}' : ')');
8706             if (q != NULL) *q = NULLCHAR;
8707             p++;
8708         }
8709         GameEnds(moveType, p, GE_FILE);
8710         done = TRUE;
8711         if (cmailMsgLoaded) {
8712             ClearHighlights();
8713             flipView = WhiteOnMove(currentMove);
8714             if (moveType == GameUnfinished) flipView = !flipView;
8715             if (appData.debugMode)
8716               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8717         }
8718         break;
8719
8720       case (ChessMove) 0:       /* end of file */
8721         if (appData.debugMode)
8722           fprintf(debugFP, "Parser hit end of file\n");
8723         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8724                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8725           case MT_NONE:
8726           case MT_CHECK:
8727             break;
8728           case MT_CHECKMATE:
8729           case MT_STAINMATE:
8730             if (WhiteOnMove(currentMove)) {
8731                 GameEnds(BlackWins, "Black mates", GE_FILE);
8732             } else {
8733                 GameEnds(WhiteWins, "White mates", GE_FILE);
8734             }
8735             break;
8736           case MT_STALEMATE:
8737             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8738             break;
8739         }
8740         done = TRUE;
8741         break;
8742
8743       case MoveNumberOne:
8744         if (lastLoadGameStart == GNUChessGame) {
8745             /* GNUChessGames have numbers, but they aren't move numbers */
8746             if (appData.debugMode)
8747               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8748                       yy_text, (int) moveType);
8749             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8750         }
8751         /* else fall thru */
8752
8753       case XBoardGame:
8754       case GNUChessGame:
8755       case PGNTag:
8756         /* Reached start of next game in file */
8757         if (appData.debugMode)
8758           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8759         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8760                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8761           case MT_NONE:
8762           case MT_CHECK:
8763             break;
8764           case MT_CHECKMATE:
8765           case MT_STAINMATE:
8766             if (WhiteOnMove(currentMove)) {
8767                 GameEnds(BlackWins, "Black mates", GE_FILE);
8768             } else {
8769                 GameEnds(WhiteWins, "White mates", GE_FILE);
8770             }
8771             break;
8772           case MT_STALEMATE:
8773             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8774             break;
8775         }
8776         done = TRUE;
8777         break;
8778
8779       case PositionDiagram:     /* should not happen; ignore */
8780       case ElapsedTime:         /* ignore */
8781       case NAG:                 /* ignore */
8782         if (appData.debugMode)
8783           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8784                   yy_text, (int) moveType);
8785         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8786
8787       case IllegalMove:
8788         if (appData.testLegality) {
8789             if (appData.debugMode)
8790               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8791             sprintf(move, _("Illegal move: %d.%s%s"),
8792                     (forwardMostMove / 2) + 1,
8793                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8794             DisplayError(move, 0);
8795             done = TRUE;
8796         } else {
8797             if (appData.debugMode)
8798               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8799                       yy_text, currentMoveString);
8800             fromX = currentMoveString[0] - AAA;
8801             fromY = currentMoveString[1] - ONE;
8802             toX = currentMoveString[2] - AAA;
8803             toY = currentMoveString[3] - ONE;
8804             promoChar = currentMoveString[4];
8805         }
8806         break;
8807
8808       case AmbiguousMove:
8809         if (appData.debugMode)
8810           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8811         sprintf(move, _("Ambiguous move: %d.%s%s"),
8812                 (forwardMostMove / 2) + 1,
8813                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8814         DisplayError(move, 0);
8815         done = TRUE;
8816         break;
8817
8818       default:
8819       case ImpossibleMove:
8820         if (appData.debugMode)
8821           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8822         sprintf(move, _("Illegal move: %d.%s%s"),
8823                 (forwardMostMove / 2) + 1,
8824                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8825         DisplayError(move, 0);
8826         done = TRUE;
8827         break;
8828     }
8829
8830     if (done) {
8831         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8832             DrawPosition(FALSE, boards[currentMove]);
8833             DisplayBothClocks();
8834             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8835               DisplayComment(currentMove - 1, commentList[currentMove]);
8836         }
8837         (void) StopLoadGameTimer();
8838         gameFileFP = NULL;
8839         cmailOldMove = forwardMostMove;
8840         return FALSE;
8841     } else {
8842         /* currentMoveString is set as a side-effect of yylex */
8843         strcat(currentMoveString, "\n");
8844         strcpy(moveList[forwardMostMove], currentMoveString);
8845         
8846         thinkOutput[0] = NULLCHAR;
8847         MakeMove(fromX, fromY, toX, toY, promoChar);
8848         currentMove = forwardMostMove;
8849         return TRUE;
8850     }
8851 }
8852
8853 /* Load the nth game from the given file */
8854 int
8855 LoadGameFromFile(filename, n, title, useList)
8856      char *filename;
8857      int n;
8858      char *title;
8859      /*Boolean*/ int useList;
8860 {
8861     FILE *f;
8862     char buf[MSG_SIZ];
8863
8864     if (strcmp(filename, "-") == 0) {
8865         f = stdin;
8866         title = "stdin";
8867     } else {
8868         f = fopen(filename, "rb");
8869         if (f == NULL) {
8870           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8871             DisplayError(buf, errno);
8872             return FALSE;
8873         }
8874     }
8875     if (fseek(f, 0, 0) == -1) {
8876         /* f is not seekable; probably a pipe */
8877         useList = FALSE;
8878     }
8879     if (useList && n == 0) {
8880         int error = GameListBuild(f);
8881         if (error) {
8882             DisplayError(_("Cannot build game list"), error);
8883         } else if (!ListEmpty(&gameList) &&
8884                    ((ListGame *) gameList.tailPred)->number > 1) {
8885             GameListPopUp(f, title);
8886             return TRUE;
8887         }
8888         GameListDestroy();
8889         n = 1;
8890     }
8891     if (n == 0) n = 1;
8892     return LoadGame(f, n, title, FALSE);
8893 }
8894
8895
8896 void
8897 MakeRegisteredMove()
8898 {
8899     int fromX, fromY, toX, toY;
8900     char promoChar;
8901     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8902         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8903           case CMAIL_MOVE:
8904           case CMAIL_DRAW:
8905             if (appData.debugMode)
8906               fprintf(debugFP, "Restoring %s for game %d\n",
8907                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8908     
8909             thinkOutput[0] = NULLCHAR;
8910             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8911             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8912             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8913             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8914             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8915             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8916             MakeMove(fromX, fromY, toX, toY, promoChar);
8917             ShowMove(fromX, fromY, toX, toY);
8918               
8919             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8920                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8921               case MT_NONE:
8922               case MT_CHECK:
8923                 break;
8924                 
8925               case MT_CHECKMATE:
8926               case MT_STAINMATE:
8927                 if (WhiteOnMove(currentMove)) {
8928                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8929                 } else {
8930                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8931                 }
8932                 break;
8933                 
8934               case MT_STALEMATE:
8935                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8936                 break;
8937             }
8938
8939             break;
8940             
8941           case CMAIL_RESIGN:
8942             if (WhiteOnMove(currentMove)) {
8943                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8944             } else {
8945                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8946             }
8947             break;
8948             
8949           case CMAIL_ACCEPT:
8950             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8951             break;
8952               
8953           default:
8954             break;
8955         }
8956     }
8957
8958     return;
8959 }
8960
8961 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8962 int
8963 CmailLoadGame(f, gameNumber, title, useList)
8964      FILE *f;
8965      int gameNumber;
8966      char *title;
8967      int useList;
8968 {
8969     int retVal;
8970
8971     if (gameNumber > nCmailGames) {
8972         DisplayError(_("No more games in this message"), 0);
8973         return FALSE;
8974     }
8975     if (f == lastLoadGameFP) {
8976         int offset = gameNumber - lastLoadGameNumber;
8977         if (offset == 0) {
8978             cmailMsg[0] = NULLCHAR;
8979             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8980                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8981                 nCmailMovesRegistered--;
8982             }
8983             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8984             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8985                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8986             }
8987         } else {
8988             if (! RegisterMove()) return FALSE;
8989         }
8990     }
8991
8992     retVal = LoadGame(f, gameNumber, title, useList);
8993
8994     /* Make move registered during previous look at this game, if any */
8995     MakeRegisteredMove();
8996
8997     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8998         commentList[currentMove]
8999           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9000         DisplayComment(currentMove - 1, commentList[currentMove]);
9001     }
9002
9003     return retVal;
9004 }
9005
9006 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9007 int
9008 ReloadGame(offset)
9009      int offset;
9010 {
9011     int gameNumber = lastLoadGameNumber + offset;
9012     if (lastLoadGameFP == NULL) {
9013         DisplayError(_("No game has been loaded yet"), 0);
9014         return FALSE;
9015     }
9016     if (gameNumber <= 0) {
9017         DisplayError(_("Can't back up any further"), 0);
9018         return FALSE;
9019     }
9020     if (cmailMsgLoaded) {
9021         return CmailLoadGame(lastLoadGameFP, gameNumber,
9022                              lastLoadGameTitle, lastLoadGameUseList);
9023     } else {
9024         return LoadGame(lastLoadGameFP, gameNumber,
9025                         lastLoadGameTitle, lastLoadGameUseList);
9026     }
9027 }
9028
9029
9030
9031 /* Load the nth game from open file f */
9032 int
9033 LoadGame(f, gameNumber, title, useList)
9034      FILE *f;
9035      int gameNumber;
9036      char *title;
9037      int useList;
9038 {
9039     ChessMove cm;
9040     char buf[MSG_SIZ];
9041     int gn = gameNumber;
9042     ListGame *lg = NULL;
9043     int numPGNTags = 0;
9044     int err;
9045     GameMode oldGameMode;
9046     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9047
9048     if (appData.debugMode) 
9049         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9050
9051     if (gameMode == Training )
9052         SetTrainingModeOff();
9053
9054     oldGameMode = gameMode;
9055     if (gameMode != BeginningOfGame) {
9056       Reset(FALSE, TRUE);
9057     }
9058
9059     gameFileFP = f;
9060     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9061         fclose(lastLoadGameFP);
9062     }
9063
9064     if (useList) {
9065         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9066         
9067         if (lg) {
9068             fseek(f, lg->offset, 0);
9069             GameListHighlight(gameNumber);
9070             gn = 1;
9071         }
9072         else {
9073             DisplayError(_("Game number out of range"), 0);
9074             return FALSE;
9075         }
9076     } else {
9077         GameListDestroy();
9078         if (fseek(f, 0, 0) == -1) {
9079             if (f == lastLoadGameFP ?
9080                 gameNumber == lastLoadGameNumber + 1 :
9081                 gameNumber == 1) {
9082                 gn = 1;
9083             } else {
9084                 DisplayError(_("Can't seek on game file"), 0);
9085                 return FALSE;
9086             }
9087         }
9088     }
9089     lastLoadGameFP = f;
9090     lastLoadGameNumber = gameNumber;
9091     strcpy(lastLoadGameTitle, title);
9092     lastLoadGameUseList = useList;
9093
9094     yynewfile(f);
9095
9096     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9097       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9098                 lg->gameInfo.black);
9099             DisplayTitle(buf);
9100     } else if (*title != NULLCHAR) {
9101         if (gameNumber > 1) {
9102             sprintf(buf, "%s %d", title, gameNumber);
9103             DisplayTitle(buf);
9104         } else {
9105             DisplayTitle(title);
9106         }
9107     }
9108
9109     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9110         gameMode = PlayFromGameFile;
9111         ModeHighlight();
9112     }
9113
9114     currentMove = forwardMostMove = backwardMostMove = 0;
9115     CopyBoard(boards[0], initialPosition);
9116     StopClocks();
9117
9118     /*
9119      * Skip the first gn-1 games in the file.
9120      * Also skip over anything that precedes an identifiable 
9121      * start of game marker, to avoid being confused by 
9122      * garbage at the start of the file.  Currently 
9123      * recognized start of game markers are the move number "1",
9124      * the pattern "gnuchess .* game", the pattern
9125      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9126      * A game that starts with one of the latter two patterns
9127      * will also have a move number 1, possibly
9128      * following a position diagram.
9129      * 5-4-02: Let's try being more lenient and allowing a game to
9130      * start with an unnumbered move.  Does that break anything?
9131      */
9132     cm = lastLoadGameStart = (ChessMove) 0;
9133     while (gn > 0) {
9134         yyboardindex = forwardMostMove;
9135         cm = (ChessMove) yylex();
9136         switch (cm) {
9137           case (ChessMove) 0:
9138             if (cmailMsgLoaded) {
9139                 nCmailGames = CMAIL_MAX_GAMES - gn;
9140             } else {
9141                 Reset(TRUE, TRUE);
9142                 DisplayError(_("Game not found in file"), 0);
9143             }
9144             return FALSE;
9145
9146           case GNUChessGame:
9147           case XBoardGame:
9148             gn--;
9149             lastLoadGameStart = cm;
9150             break;
9151             
9152           case MoveNumberOne:
9153             switch (lastLoadGameStart) {
9154               case GNUChessGame:
9155               case XBoardGame:
9156               case PGNTag:
9157                 break;
9158               case MoveNumberOne:
9159               case (ChessMove) 0:
9160                 gn--;           /* count this game */
9161                 lastLoadGameStart = cm;
9162                 break;
9163               default:
9164                 /* impossible */
9165                 break;
9166             }
9167             break;
9168
9169           case PGNTag:
9170             switch (lastLoadGameStart) {
9171               case GNUChessGame:
9172               case PGNTag:
9173               case MoveNumberOne:
9174               case (ChessMove) 0:
9175                 gn--;           /* count this game */
9176                 lastLoadGameStart = cm;
9177                 break;
9178               case XBoardGame:
9179                 lastLoadGameStart = cm; /* game counted already */
9180                 break;
9181               default:
9182                 /* impossible */
9183                 break;
9184             }
9185             if (gn > 0) {
9186                 do {
9187                     yyboardindex = forwardMostMove;
9188                     cm = (ChessMove) yylex();
9189                 } while (cm == PGNTag || cm == Comment);
9190             }
9191             break;
9192
9193           case WhiteWins:
9194           case BlackWins:
9195           case GameIsDrawn:
9196             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9197                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9198                     != CMAIL_OLD_RESULT) {
9199                     nCmailResults ++ ;
9200                     cmailResult[  CMAIL_MAX_GAMES
9201                                 - gn - 1] = CMAIL_OLD_RESULT;
9202                 }
9203             }
9204             break;
9205
9206           case NormalMove:
9207             /* Only a NormalMove can be at the start of a game
9208              * without a position diagram. */
9209             if (lastLoadGameStart == (ChessMove) 0) {
9210               gn--;
9211               lastLoadGameStart = MoveNumberOne;
9212             }
9213             break;
9214
9215           default:
9216             break;
9217         }
9218     }
9219     
9220     if (appData.debugMode)
9221       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9222
9223     if (cm == XBoardGame) {
9224         /* Skip any header junk before position diagram and/or move 1 */
9225         for (;;) {
9226             yyboardindex = forwardMostMove;
9227             cm = (ChessMove) yylex();
9228
9229             if (cm == (ChessMove) 0 ||
9230                 cm == GNUChessGame || cm == XBoardGame) {
9231                 /* Empty game; pretend end-of-file and handle later */
9232                 cm = (ChessMove) 0;
9233                 break;
9234             }
9235
9236             if (cm == MoveNumberOne || cm == PositionDiagram ||
9237                 cm == PGNTag || cm == Comment)
9238               break;
9239         }
9240     } else if (cm == GNUChessGame) {
9241         if (gameInfo.event != NULL) {
9242             free(gameInfo.event);
9243         }
9244         gameInfo.event = StrSave(yy_text);
9245     }   
9246
9247     startedFromSetupPosition = FALSE;
9248     while (cm == PGNTag) {
9249         if (appData.debugMode) 
9250           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9251         err = ParsePGNTag(yy_text, &gameInfo);
9252         if (!err) numPGNTags++;
9253
9254         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9255         if(gameInfo.variant != oldVariant) {
9256             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9257             InitPosition(TRUE);
9258             oldVariant = gameInfo.variant;
9259             if (appData.debugMode) 
9260               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9261         }
9262
9263
9264         if (gameInfo.fen != NULL) {
9265           Board initial_position;
9266           startedFromSetupPosition = TRUE;
9267           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9268             Reset(TRUE, TRUE);
9269             DisplayError(_("Bad FEN position in file"), 0);
9270             return FALSE;
9271           }
9272           CopyBoard(boards[0], initial_position);
9273           if (blackPlaysFirst) {
9274             currentMove = forwardMostMove = backwardMostMove = 1;
9275             CopyBoard(boards[1], initial_position);
9276             strcpy(moveList[0], "");
9277             strcpy(parseList[0], "");
9278             timeRemaining[0][1] = whiteTimeRemaining;
9279             timeRemaining[1][1] = blackTimeRemaining;
9280             if (commentList[0] != NULL) {
9281               commentList[1] = commentList[0];
9282               commentList[0] = NULL;
9283             }
9284           } else {
9285             currentMove = forwardMostMove = backwardMostMove = 0;
9286           }
9287           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9288           {   int i;
9289               initialRulePlies = FENrulePlies;
9290               epStatus[forwardMostMove] = FENepStatus;
9291               for( i=0; i< nrCastlingRights; i++ )
9292                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9293           }
9294           yyboardindex = forwardMostMove;
9295           free(gameInfo.fen);
9296           gameInfo.fen = NULL;
9297         }
9298
9299         yyboardindex = forwardMostMove;
9300         cm = (ChessMove) yylex();
9301
9302         /* Handle comments interspersed among the tags */
9303         while (cm == Comment) {
9304             char *p;
9305             if (appData.debugMode) 
9306               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9307             p = yy_text;
9308             if (*p == '{' || *p == '[' || *p == '(') {
9309                 p[strlen(p) - 1] = NULLCHAR;
9310                 p++;
9311             }
9312             while (*p == '\n') p++;
9313             AppendComment(currentMove, p);
9314             yyboardindex = forwardMostMove;
9315             cm = (ChessMove) yylex();
9316         }
9317     }
9318
9319     /* don't rely on existence of Event tag since if game was
9320      * pasted from clipboard the Event tag may not exist
9321      */
9322     if (numPGNTags > 0){
9323         char *tags;
9324         if (gameInfo.variant == VariantNormal) {
9325           gameInfo.variant = StringToVariant(gameInfo.event);
9326         }
9327         if (!matchMode) {
9328           if( appData.autoDisplayTags ) {
9329             tags = PGNTags(&gameInfo);
9330             TagsPopUp(tags, CmailMsg());
9331             free(tags);
9332           }
9333         }
9334     } else {
9335         /* Make something up, but don't display it now */
9336         SetGameInfo();
9337         TagsPopDown();
9338     }
9339
9340     if (cm == PositionDiagram) {
9341         int i, j;
9342         char *p;
9343         Board initial_position;
9344
9345         if (appData.debugMode)
9346           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9347
9348         if (!startedFromSetupPosition) {
9349             p = yy_text;
9350             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9351               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9352                 switch (*p) {
9353                   case '[':
9354                   case '-':
9355                   case ' ':
9356                   case '\t':
9357                   case '\n':
9358                   case '\r':
9359                     break;
9360                   default:
9361                     initial_position[i][j++] = CharToPiece(*p);
9362                     break;
9363                 }
9364             while (*p == ' ' || *p == '\t' ||
9365                    *p == '\n' || *p == '\r') p++;
9366         
9367             if (strncmp(p, "black", strlen("black"))==0)
9368               blackPlaysFirst = TRUE;
9369             else
9370               blackPlaysFirst = FALSE;
9371             startedFromSetupPosition = TRUE;
9372         
9373             CopyBoard(boards[0], initial_position);
9374             if (blackPlaysFirst) {
9375                 currentMove = forwardMostMove = backwardMostMove = 1;
9376                 CopyBoard(boards[1], initial_position);
9377                 strcpy(moveList[0], "");
9378                 strcpy(parseList[0], "");
9379                 timeRemaining[0][1] = whiteTimeRemaining;
9380                 timeRemaining[1][1] = blackTimeRemaining;
9381                 if (commentList[0] != NULL) {
9382                     commentList[1] = commentList[0];
9383                     commentList[0] = NULL;
9384                 }
9385             } else {
9386                 currentMove = forwardMostMove = backwardMostMove = 0;
9387             }
9388         }
9389         yyboardindex = forwardMostMove;
9390         cm = (ChessMove) yylex();
9391     }
9392
9393     if (first.pr == NoProc) {
9394         StartChessProgram(&first);
9395     }
9396     InitChessProgram(&first, FALSE);
9397     SendToProgram("force\n", &first);
9398     if (startedFromSetupPosition) {
9399         SendBoard(&first, forwardMostMove);
9400     if (appData.debugMode) {
9401         fprintf(debugFP, "Load Game\n");
9402     }
9403         DisplayBothClocks();
9404     }      
9405
9406     /* [HGM] server: flag to write setup moves in broadcast file as one */
9407     loadFlag = appData.suppressLoadMoves;
9408
9409     while (cm == Comment) {
9410         char *p;
9411         if (appData.debugMode) 
9412           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9413         p = yy_text;
9414         if (*p == '{' || *p == '[' || *p == '(') {
9415             p[strlen(p) - 1] = NULLCHAR;
9416             p++;
9417         }
9418         while (*p == '\n') p++;
9419         AppendComment(currentMove, p);
9420         yyboardindex = forwardMostMove;
9421         cm = (ChessMove) yylex();
9422     }
9423
9424     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9425         cm == WhiteWins || cm == BlackWins ||
9426         cm == GameIsDrawn || cm == GameUnfinished) {
9427         DisplayMessage("", _("No moves in game"));
9428         if (cmailMsgLoaded) {
9429             if (appData.debugMode)
9430               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9431             ClearHighlights();
9432             flipView = FALSE;
9433         }
9434         DrawPosition(FALSE, boards[currentMove]);
9435         DisplayBothClocks();
9436         gameMode = EditGame;
9437         ModeHighlight();
9438         gameFileFP = NULL;
9439         cmailOldMove = 0;
9440         return TRUE;
9441     }
9442
9443     // [HGM] PV info: routine tests if comment empty
9444     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9445         DisplayComment(currentMove - 1, commentList[currentMove]);
9446     }
9447     if (!matchMode && appData.timeDelay != 0) 
9448       DrawPosition(FALSE, boards[currentMove]);
9449
9450     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9451       programStats.ok_to_send = 1;
9452     }
9453
9454     /* if the first token after the PGN tags is a move
9455      * and not move number 1, retrieve it from the parser 
9456      */
9457     if (cm != MoveNumberOne)
9458         LoadGameOneMove(cm);
9459
9460     /* load the remaining moves from the file */
9461     while (LoadGameOneMove((ChessMove)0)) {
9462       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9463       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9464     }
9465
9466     /* rewind to the start of the game */
9467     currentMove = backwardMostMove;
9468
9469     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9470
9471     if (oldGameMode == AnalyzeFile ||
9472         oldGameMode == AnalyzeMode) {
9473       AnalyzeFileEvent();
9474     }
9475
9476     if (matchMode || appData.timeDelay == 0) {
9477       ToEndEvent();
9478       gameMode = EditGame;
9479       ModeHighlight();
9480     } else if (appData.timeDelay > 0) {
9481       AutoPlayGameLoop();
9482     }
9483
9484     if (appData.debugMode) 
9485         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9486
9487     loadFlag = 0; /* [HGM] true game starts */
9488     return TRUE;
9489 }
9490
9491 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9492 int
9493 ReloadPosition(offset)
9494      int offset;
9495 {
9496     int positionNumber = lastLoadPositionNumber + offset;
9497     if (lastLoadPositionFP == NULL) {
9498         DisplayError(_("No position has been loaded yet"), 0);
9499         return FALSE;
9500     }
9501     if (positionNumber <= 0) {
9502         DisplayError(_("Can't back up any further"), 0);
9503         return FALSE;
9504     }
9505     return LoadPosition(lastLoadPositionFP, positionNumber,
9506                         lastLoadPositionTitle);
9507 }
9508
9509 /* Load the nth position from the given file */
9510 int
9511 LoadPositionFromFile(filename, n, title)
9512      char *filename;
9513      int n;
9514      char *title;
9515 {
9516     FILE *f;
9517     char buf[MSG_SIZ];
9518
9519     if (strcmp(filename, "-") == 0) {
9520         return LoadPosition(stdin, n, "stdin");
9521     } else {
9522         f = fopen(filename, "rb");
9523         if (f == NULL) {
9524             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9525             DisplayError(buf, errno);
9526             return FALSE;
9527         } else {
9528             return LoadPosition(f, n, title);
9529         }
9530     }
9531 }
9532
9533 /* Load the nth position from the given open file, and close it */
9534 int
9535 LoadPosition(f, positionNumber, title)
9536      FILE *f;
9537      int positionNumber;
9538      char *title;
9539 {
9540     char *p, line[MSG_SIZ];
9541     Board initial_position;
9542     int i, j, fenMode, pn;
9543     
9544     if (gameMode == Training )
9545         SetTrainingModeOff();
9546
9547     if (gameMode != BeginningOfGame) {
9548         Reset(FALSE, TRUE);
9549     }
9550     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9551         fclose(lastLoadPositionFP);
9552     }
9553     if (positionNumber == 0) positionNumber = 1;
9554     lastLoadPositionFP = f;
9555     lastLoadPositionNumber = positionNumber;
9556     strcpy(lastLoadPositionTitle, title);
9557     if (first.pr == NoProc) {
9558       StartChessProgram(&first);
9559       InitChessProgram(&first, FALSE);
9560     }    
9561     pn = positionNumber;
9562     if (positionNumber < 0) {
9563         /* Negative position number means to seek to that byte offset */
9564         if (fseek(f, -positionNumber, 0) == -1) {
9565             DisplayError(_("Can't seek on position file"), 0);
9566             return FALSE;
9567         };
9568         pn = 1;
9569     } else {
9570         if (fseek(f, 0, 0) == -1) {
9571             if (f == lastLoadPositionFP ?
9572                 positionNumber == lastLoadPositionNumber + 1 :
9573                 positionNumber == 1) {
9574                 pn = 1;
9575             } else {
9576                 DisplayError(_("Can't seek on position file"), 0);
9577                 return FALSE;
9578             }
9579         }
9580     }
9581     /* See if this file is FEN or old-style xboard */
9582     if (fgets(line, MSG_SIZ, f) == NULL) {
9583         DisplayError(_("Position not found in file"), 0);
9584         return FALSE;
9585     }
9586     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9587     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9588
9589     if (pn >= 2) {
9590         if (fenMode || line[0] == '#') pn--;
9591         while (pn > 0) {
9592             /* skip positions before number pn */
9593             if (fgets(line, MSG_SIZ, f) == NULL) {
9594                 Reset(TRUE, TRUE);
9595                 DisplayError(_("Position not found in file"), 0);
9596                 return FALSE;
9597             }
9598             if (fenMode || line[0] == '#') pn--;
9599         }
9600     }
9601
9602     if (fenMode) {
9603         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9604             DisplayError(_("Bad FEN position in file"), 0);
9605             return FALSE;
9606         }
9607     } else {
9608         (void) fgets(line, MSG_SIZ, f);
9609         (void) fgets(line, MSG_SIZ, f);
9610     
9611         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9612             (void) fgets(line, MSG_SIZ, f);
9613             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9614                 if (*p == ' ')
9615                   continue;
9616                 initial_position[i][j++] = CharToPiece(*p);
9617             }
9618         }
9619     
9620         blackPlaysFirst = FALSE;
9621         if (!feof(f)) {
9622             (void) fgets(line, MSG_SIZ, f);
9623             if (strncmp(line, "black", strlen("black"))==0)
9624               blackPlaysFirst = TRUE;
9625         }
9626     }
9627     startedFromSetupPosition = TRUE;
9628     
9629     SendToProgram("force\n", &first);
9630     CopyBoard(boards[0], initial_position);
9631     if (blackPlaysFirst) {
9632         currentMove = forwardMostMove = backwardMostMove = 1;
9633         strcpy(moveList[0], "");
9634         strcpy(parseList[0], "");
9635         CopyBoard(boards[1], initial_position);
9636         DisplayMessage("", _("Black to play"));
9637     } else {
9638         currentMove = forwardMostMove = backwardMostMove = 0;
9639         DisplayMessage("", _("White to play"));
9640     }
9641           /* [HGM] copy FEN attributes as well */
9642           {   int i;
9643               initialRulePlies = FENrulePlies;
9644               epStatus[forwardMostMove] = FENepStatus;
9645               for( i=0; i< nrCastlingRights; i++ )
9646                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9647           }
9648     SendBoard(&first, forwardMostMove);
9649     if (appData.debugMode) {
9650 int i, j;
9651   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9652   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9653         fprintf(debugFP, "Load Position\n");
9654     }
9655
9656     if (positionNumber > 1) {
9657         sprintf(line, "%s %d", title, positionNumber);
9658         DisplayTitle(line);
9659     } else {
9660         DisplayTitle(title);
9661     }
9662     gameMode = EditGame;
9663     ModeHighlight();
9664     ResetClocks();
9665     timeRemaining[0][1] = whiteTimeRemaining;
9666     timeRemaining[1][1] = blackTimeRemaining;
9667     DrawPosition(FALSE, boards[currentMove]);
9668    
9669     return TRUE;
9670 }
9671
9672
9673 void
9674 CopyPlayerNameIntoFileName(dest, src)
9675      char **dest, *src;
9676 {
9677     while (*src != NULLCHAR && *src != ',') {
9678         if (*src == ' ') {
9679             *(*dest)++ = '_';
9680             src++;
9681         } else {
9682             *(*dest)++ = *src++;
9683         }
9684     }
9685 }
9686
9687 char *DefaultFileName(ext)
9688      char *ext;
9689 {
9690     static char def[MSG_SIZ];
9691     char *p;
9692
9693     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9694         p = def;
9695         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9696         *p++ = '-';
9697         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9698         *p++ = '.';
9699         strcpy(p, ext);
9700     } else {
9701         def[0] = NULLCHAR;
9702     }
9703     return def;
9704 }
9705
9706 /* Save the current game to the given file */
9707 int
9708 SaveGameToFile(filename, append)
9709      char *filename;
9710      int append;
9711 {
9712     FILE *f;
9713     char buf[MSG_SIZ];
9714
9715     if (strcmp(filename, "-") == 0) {
9716         return SaveGame(stdout, 0, NULL);
9717     } else {
9718         f = fopen(filename, append ? "a" : "w");
9719         if (f == NULL) {
9720             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9721             DisplayError(buf, errno);
9722             return FALSE;
9723         } else {
9724             return SaveGame(f, 0, NULL);
9725         }
9726     }
9727 }
9728
9729 char *
9730 SavePart(str)
9731      char *str;
9732 {
9733     static char buf[MSG_SIZ];
9734     char *p;
9735     
9736     p = strchr(str, ' ');
9737     if (p == NULL) return str;
9738     strncpy(buf, str, p - str);
9739     buf[p - str] = NULLCHAR;
9740     return buf;
9741 }
9742
9743 #define PGN_MAX_LINE 75
9744
9745 #define PGN_SIDE_WHITE  0
9746 #define PGN_SIDE_BLACK  1
9747
9748 /* [AS] */
9749 static int FindFirstMoveOutOfBook( int side )
9750 {
9751     int result = -1;
9752
9753     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9754         int index = backwardMostMove;
9755         int has_book_hit = 0;
9756
9757         if( (index % 2) != side ) {
9758             index++;
9759         }
9760
9761         while( index < forwardMostMove ) {
9762             /* Check to see if engine is in book */
9763             int depth = pvInfoList[index].depth;
9764             int score = pvInfoList[index].score;
9765             int in_book = 0;
9766
9767             if( depth <= 2 ) {
9768                 in_book = 1;
9769             }
9770             else if( score == 0 && depth == 63 ) {
9771                 in_book = 1; /* Zappa */
9772             }
9773             else if( score == 2 && depth == 99 ) {
9774                 in_book = 1; /* Abrok */
9775             }
9776
9777             has_book_hit += in_book;
9778
9779             if( ! in_book ) {
9780                 result = index;
9781
9782                 break;
9783             }
9784
9785             index += 2;
9786         }
9787     }
9788
9789     return result;
9790 }
9791
9792 /* [AS] */
9793 void GetOutOfBookInfo( char * buf )
9794 {
9795     int oob[2];
9796     int i;
9797     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9798
9799     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9800     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9801
9802     *buf = '\0';
9803
9804     if( oob[0] >= 0 || oob[1] >= 0 ) {
9805         for( i=0; i<2; i++ ) {
9806             int idx = oob[i];
9807
9808             if( idx >= 0 ) {
9809                 if( i > 0 && oob[0] >= 0 ) {
9810                     strcat( buf, "   " );
9811                 }
9812
9813                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9814                 sprintf( buf+strlen(buf), "%s%.2f", 
9815                     pvInfoList[idx].score >= 0 ? "+" : "",
9816                     pvInfoList[idx].score / 100.0 );
9817             }
9818         }
9819     }
9820 }
9821
9822 /* Save game in PGN style and close the file */
9823 int
9824 SaveGamePGN(f)
9825      FILE *f;
9826 {
9827     int i, offset, linelen, newblock;
9828     time_t tm;
9829 //    char *movetext;
9830     char numtext[32];
9831     int movelen, numlen, blank;
9832     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9833
9834     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9835     
9836     tm = time((time_t *) NULL);
9837     
9838     PrintPGNTags(f, &gameInfo);
9839     
9840     if (backwardMostMove > 0 || startedFromSetupPosition) {
9841         char *fen = PositionToFEN(backwardMostMove, NULL);
9842         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9843         fprintf(f, "\n{--------------\n");
9844         PrintPosition(f, backwardMostMove);
9845         fprintf(f, "--------------}\n");
9846         free(fen);
9847     }
9848     else {
9849         /* [AS] Out of book annotation */
9850         if( appData.saveOutOfBookInfo ) {
9851             char buf[64];
9852
9853             GetOutOfBookInfo( buf );
9854
9855             if( buf[0] != '\0' ) {
9856                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9857             }
9858         }
9859
9860         fprintf(f, "\n");
9861     }
9862
9863     i = backwardMostMove;
9864     linelen = 0;
9865     newblock = TRUE;
9866
9867     while (i < forwardMostMove) {
9868         /* Print comments preceding this move */
9869         if (commentList[i] != NULL) {
9870             if (linelen > 0) fprintf(f, "\n");
9871             fprintf(f, "{\n%s}\n", commentList[i]);
9872             linelen = 0;
9873             newblock = TRUE;
9874         }
9875
9876         /* Format move number */
9877         if ((i % 2) == 0) {
9878             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9879         } else {
9880             if (newblock) {
9881                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9882             } else {
9883                 numtext[0] = NULLCHAR;
9884             }
9885         }
9886         numlen = strlen(numtext);
9887         newblock = FALSE;
9888
9889         /* Print move number */
9890         blank = linelen > 0 && numlen > 0;
9891         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9892             fprintf(f, "\n");
9893             linelen = 0;
9894             blank = 0;
9895         }
9896         if (blank) {
9897             fprintf(f, " ");
9898             linelen++;
9899         }
9900         fprintf(f, "%s", numtext);
9901         linelen += numlen;
9902
9903         /* Get move */
9904         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9905         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9906
9907         /* Print move */
9908         blank = linelen > 0 && movelen > 0;
9909         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9910             fprintf(f, "\n");
9911             linelen = 0;
9912             blank = 0;
9913         }
9914         if (blank) {
9915             fprintf(f, " ");
9916             linelen++;
9917         }
9918         fprintf(f, "%s", move_buffer);
9919         linelen += movelen;
9920
9921         /* [AS] Add PV info if present */
9922         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9923             /* [HGM] add time */
9924             char buf[MSG_SIZ]; int seconds = 0;
9925
9926             if(i >= backwardMostMove) {
9927                 if(WhiteOnMove(i))
9928                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9929                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9930                 else
9931                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9932                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9933             }
9934             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9935
9936             if( seconds <= 0) buf[0] = 0; else
9937             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9938                 seconds = (seconds + 4)/10; // round to full seconds
9939                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9940                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9941             }
9942
9943             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9944                 pvInfoList[i].score >= 0 ? "+" : "",
9945                 pvInfoList[i].score / 100.0,
9946                 pvInfoList[i].depth,
9947                 buf );
9948
9949             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9950
9951             /* Print score/depth */
9952             blank = linelen > 0 && movelen > 0;
9953             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9954                 fprintf(f, "\n");
9955                 linelen = 0;
9956                 blank = 0;
9957             }
9958             if (blank) {
9959                 fprintf(f, " ");
9960                 linelen++;
9961             }
9962             fprintf(f, "%s", move_buffer);
9963             linelen += movelen;
9964         }
9965
9966         i++;
9967     }
9968     
9969     /* Start a new line */
9970     if (linelen > 0) fprintf(f, "\n");
9971
9972     /* Print comments after last move */
9973     if (commentList[i] != NULL) {
9974         fprintf(f, "{\n%s}\n", commentList[i]);
9975     }
9976
9977     /* Print result */
9978     if (gameInfo.resultDetails != NULL &&
9979         gameInfo.resultDetails[0] != NULLCHAR) {
9980         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9981                 PGNResult(gameInfo.result));
9982     } else {
9983         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9984     }
9985
9986     fclose(f);
9987     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9988     return TRUE;
9989 }
9990
9991 /* Save game in old style and close the file */
9992 int
9993 SaveGameOldStyle(f)
9994      FILE *f;
9995 {
9996     int i, offset;
9997     time_t tm;
9998     
9999     tm = time((time_t *) NULL);
10000     
10001     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10002     PrintOpponents(f);
10003     
10004     if (backwardMostMove > 0 || startedFromSetupPosition) {
10005         fprintf(f, "\n[--------------\n");
10006         PrintPosition(f, backwardMostMove);
10007         fprintf(f, "--------------]\n");
10008     } else {
10009         fprintf(f, "\n");
10010     }
10011
10012     i = backwardMostMove;
10013     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10014
10015     while (i < forwardMostMove) {
10016         if (commentList[i] != NULL) {
10017             fprintf(f, "[%s]\n", commentList[i]);
10018         }
10019
10020         if ((i % 2) == 1) {
10021             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10022             i++;
10023         } else {
10024             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10025             i++;
10026             if (commentList[i] != NULL) {
10027                 fprintf(f, "\n");
10028                 continue;
10029             }
10030             if (i >= forwardMostMove) {
10031                 fprintf(f, "\n");
10032                 break;
10033             }
10034             fprintf(f, "%s\n", parseList[i]);
10035             i++;
10036         }
10037     }
10038     
10039     if (commentList[i] != NULL) {
10040         fprintf(f, "[%s]\n", commentList[i]);
10041     }
10042
10043     /* This isn't really the old style, but it's close enough */
10044     if (gameInfo.resultDetails != NULL &&
10045         gameInfo.resultDetails[0] != NULLCHAR) {
10046         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10047                 gameInfo.resultDetails);
10048     } else {
10049         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10050     }
10051
10052     fclose(f);
10053     return TRUE;
10054 }
10055
10056 /* Save the current game to open file f and close the file */
10057 int
10058 SaveGame(f, dummy, dummy2)
10059      FILE *f;
10060      int dummy;
10061      char *dummy2;
10062 {
10063     if (gameMode == EditPosition) EditPositionDone();
10064     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10065     if (appData.oldSaveStyle)
10066       return SaveGameOldStyle(f);
10067     else
10068       return SaveGamePGN(f);
10069 }
10070
10071 /* Save the current position to the given file */
10072 int
10073 SavePositionToFile(filename)
10074      char *filename;
10075 {
10076     FILE *f;
10077     char buf[MSG_SIZ];
10078
10079     if (strcmp(filename, "-") == 0) {
10080         return SavePosition(stdout, 0, NULL);
10081     } else {
10082         f = fopen(filename, "a");
10083         if (f == NULL) {
10084             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10085             DisplayError(buf, errno);
10086             return FALSE;
10087         } else {
10088             SavePosition(f, 0, NULL);
10089             return TRUE;
10090         }
10091     }
10092 }
10093
10094 /* Save the current position to the given open file and close the file */
10095 int
10096 SavePosition(f, dummy, dummy2)
10097      FILE *f;
10098      int dummy;
10099      char *dummy2;
10100 {
10101     time_t tm;
10102     char *fen;
10103     
10104     if (appData.oldSaveStyle) {
10105         tm = time((time_t *) NULL);
10106     
10107         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10108         PrintOpponents(f);
10109         fprintf(f, "[--------------\n");
10110         PrintPosition(f, currentMove);
10111         fprintf(f, "--------------]\n");
10112     } else {
10113         fen = PositionToFEN(currentMove, NULL);
10114         fprintf(f, "%s\n", fen);
10115         free(fen);
10116     }
10117     fclose(f);
10118     return TRUE;
10119 }
10120
10121 void
10122 ReloadCmailMsgEvent(unregister)
10123      int unregister;
10124 {
10125 #if !WIN32
10126     static char *inFilename = NULL;
10127     static char *outFilename;
10128     int i;
10129     struct stat inbuf, outbuf;
10130     int status;
10131     
10132     /* Any registered moves are unregistered if unregister is set, */
10133     /* i.e. invoked by the signal handler */
10134     if (unregister) {
10135         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10136             cmailMoveRegistered[i] = FALSE;
10137             if (cmailCommentList[i] != NULL) {
10138                 free(cmailCommentList[i]);
10139                 cmailCommentList[i] = NULL;
10140             }
10141         }
10142         nCmailMovesRegistered = 0;
10143     }
10144
10145     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10146         cmailResult[i] = CMAIL_NOT_RESULT;
10147     }
10148     nCmailResults = 0;
10149
10150     if (inFilename == NULL) {
10151         /* Because the filenames are static they only get malloced once  */
10152         /* and they never get freed                                      */
10153         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10154         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10155
10156         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10157         sprintf(outFilename, "%s.out", appData.cmailGameName);
10158     }
10159     
10160     status = stat(outFilename, &outbuf);
10161     if (status < 0) {
10162         cmailMailedMove = FALSE;
10163     } else {
10164         status = stat(inFilename, &inbuf);
10165         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10166     }
10167     
10168     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10169        counts the games, notes how each one terminated, etc.
10170        
10171        It would be nice to remove this kludge and instead gather all
10172        the information while building the game list.  (And to keep it
10173        in the game list nodes instead of having a bunch of fixed-size
10174        parallel arrays.)  Note this will require getting each game's
10175        termination from the PGN tags, as the game list builder does
10176        not process the game moves.  --mann
10177        */
10178     cmailMsgLoaded = TRUE;
10179     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10180     
10181     /* Load first game in the file or popup game menu */
10182     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10183
10184 #endif /* !WIN32 */
10185     return;
10186 }
10187
10188 int
10189 RegisterMove()
10190 {
10191     FILE *f;
10192     char string[MSG_SIZ];
10193
10194     if (   cmailMailedMove
10195         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10196         return TRUE;            /* Allow free viewing  */
10197     }
10198
10199     /* Unregister move to ensure that we don't leave RegisterMove        */
10200     /* with the move registered when the conditions for registering no   */
10201     /* longer hold                                                       */
10202     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10203         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10204         nCmailMovesRegistered --;
10205
10206         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10207           {
10208               free(cmailCommentList[lastLoadGameNumber - 1]);
10209               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10210           }
10211     }
10212
10213     if (cmailOldMove == -1) {
10214         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10215         return FALSE;
10216     }
10217
10218     if (currentMove > cmailOldMove + 1) {
10219         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10220         return FALSE;
10221     }
10222
10223     if (currentMove < cmailOldMove) {
10224         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10225         return FALSE;
10226     }
10227
10228     if (forwardMostMove > currentMove) {
10229         /* Silently truncate extra moves */
10230         TruncateGame();
10231     }
10232
10233     if (   (currentMove == cmailOldMove + 1)
10234         || (   (currentMove == cmailOldMove)
10235             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10236                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10237         if (gameInfo.result != GameUnfinished) {
10238             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10239         }
10240
10241         if (commentList[currentMove] != NULL) {
10242             cmailCommentList[lastLoadGameNumber - 1]
10243               = StrSave(commentList[currentMove]);
10244         }
10245         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10246
10247         if (appData.debugMode)
10248           fprintf(debugFP, "Saving %s for game %d\n",
10249                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10250
10251         sprintf(string,
10252                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10253         
10254         f = fopen(string, "w");
10255         if (appData.oldSaveStyle) {
10256             SaveGameOldStyle(f); /* also closes the file */
10257             
10258             sprintf(string, "%s.pos.out", appData.cmailGameName);
10259             f = fopen(string, "w");
10260             SavePosition(f, 0, NULL); /* also closes the file */
10261         } else {
10262             fprintf(f, "{--------------\n");
10263             PrintPosition(f, currentMove);
10264             fprintf(f, "--------------}\n\n");
10265             
10266             SaveGame(f, 0, NULL); /* also closes the file*/
10267         }
10268         
10269         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10270         nCmailMovesRegistered ++;
10271     } else if (nCmailGames == 1) {
10272         DisplayError(_("You have not made a move yet"), 0);
10273         return FALSE;
10274     }
10275
10276     return TRUE;
10277 }
10278
10279 void
10280 MailMoveEvent()
10281 {
10282 #if !WIN32
10283     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10284     FILE *commandOutput;
10285     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10286     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10287     int nBuffers;
10288     int i;
10289     int archived;
10290     char *arcDir;
10291
10292     if (! cmailMsgLoaded) {
10293         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10294         return;
10295     }
10296
10297     if (nCmailGames == nCmailResults) {
10298         DisplayError(_("No unfinished games"), 0);
10299         return;
10300     }
10301
10302 #if CMAIL_PROHIBIT_REMAIL
10303     if (cmailMailedMove) {
10304         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);
10305         DisplayError(msg, 0);
10306         return;
10307     }
10308 #endif
10309
10310     if (! (cmailMailedMove || RegisterMove())) return;
10311     
10312     if (   cmailMailedMove
10313         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10314         sprintf(string, partCommandString,
10315                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10316         commandOutput = popen(string, "r");
10317
10318         if (commandOutput == NULL) {
10319             DisplayError(_("Failed to invoke cmail"), 0);
10320         } else {
10321             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10322                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10323             }
10324             if (nBuffers > 1) {
10325                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10326                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10327                 nBytes = MSG_SIZ - 1;
10328             } else {
10329                 (void) memcpy(msg, buffer, nBytes);
10330             }
10331             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10332
10333             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10334                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10335
10336                 archived = TRUE;
10337                 for (i = 0; i < nCmailGames; i ++) {
10338                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10339                         archived = FALSE;
10340                     }
10341                 }
10342                 if (   archived
10343                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10344                         != NULL)) {
10345                     sprintf(buffer, "%s/%s.%s.archive",
10346                             arcDir,
10347                             appData.cmailGameName,
10348                             gameInfo.date);
10349                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10350                     cmailMsgLoaded = FALSE;
10351                 }
10352             }
10353
10354             DisplayInformation(msg);
10355             pclose(commandOutput);
10356         }
10357     } else {
10358         if ((*cmailMsg) != '\0') {
10359             DisplayInformation(cmailMsg);
10360         }
10361     }
10362
10363     return;
10364 #endif /* !WIN32 */
10365 }
10366
10367 char *
10368 CmailMsg()
10369 {
10370 #if WIN32
10371     return NULL;
10372 #else
10373     int  prependComma = 0;
10374     char number[5];
10375     char string[MSG_SIZ];       /* Space for game-list */
10376     int  i;
10377     
10378     if (!cmailMsgLoaded) return "";
10379
10380     if (cmailMailedMove) {
10381         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10382     } else {
10383         /* Create a list of games left */
10384         sprintf(string, "[");
10385         for (i = 0; i < nCmailGames; i ++) {
10386             if (! (   cmailMoveRegistered[i]
10387                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10388                 if (prependComma) {
10389                     sprintf(number, ",%d", i + 1);
10390                 } else {
10391                     sprintf(number, "%d", i + 1);
10392                     prependComma = 1;
10393                 }
10394                 
10395                 strcat(string, number);
10396             }
10397         }
10398         strcat(string, "]");
10399
10400         if (nCmailMovesRegistered + nCmailResults == 0) {
10401             switch (nCmailGames) {
10402               case 1:
10403                 sprintf(cmailMsg,
10404                         _("Still need to make move for game\n"));
10405                 break;
10406                 
10407               case 2:
10408                 sprintf(cmailMsg,
10409                         _("Still need to make moves for both games\n"));
10410                 break;
10411                 
10412               default:
10413                 sprintf(cmailMsg,
10414                         _("Still need to make moves for all %d games\n"),
10415                         nCmailGames);
10416                 break;
10417             }
10418         } else {
10419             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10420               case 1:
10421                 sprintf(cmailMsg,
10422                         _("Still need to make a move for game %s\n"),
10423                         string);
10424                 break;
10425                 
10426               case 0:
10427                 if (nCmailResults == nCmailGames) {
10428                     sprintf(cmailMsg, _("No unfinished games\n"));
10429                 } else {
10430                     sprintf(cmailMsg, _("Ready to send mail\n"));
10431                 }
10432                 break;
10433                 
10434               default:
10435                 sprintf(cmailMsg,
10436                         _("Still need to make moves for games %s\n"),
10437                         string);
10438             }
10439         }
10440     }
10441     return cmailMsg;
10442 #endif /* WIN32 */
10443 }
10444
10445 void
10446 ResetGameEvent()
10447 {
10448     if (gameMode == Training)
10449       SetTrainingModeOff();
10450
10451     Reset(TRUE, TRUE);
10452     cmailMsgLoaded = FALSE;
10453     if (appData.icsActive) {
10454       SendToICS(ics_prefix);
10455       SendToICS("refresh\n");
10456     }
10457 }
10458
10459 void
10460 ExitEvent(status)
10461      int status;
10462 {
10463     exiting++;
10464     if (exiting > 2) {
10465       /* Give up on clean exit */
10466       exit(status);
10467     }
10468     if (exiting > 1) {
10469       /* Keep trying for clean exit */
10470       return;
10471     }
10472
10473     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10474
10475     if (telnetISR != NULL) {
10476       RemoveInputSource(telnetISR);
10477     }
10478     if (icsPR != NoProc) {
10479       DestroyChildProcess(icsPR, TRUE);
10480     }
10481
10482     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10483     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10484
10485     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10486     /* make sure this other one finishes before killing it!                  */
10487     if(endingGame) { int count = 0;
10488         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10489         while(endingGame && count++ < 10) DoSleep(1);
10490         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10491     }
10492
10493     /* Kill off chess programs */
10494     if (first.pr != NoProc) {
10495         ExitAnalyzeMode();
10496         
10497         DoSleep( appData.delayBeforeQuit );
10498         SendToProgram("quit\n", &first);
10499         DoSleep( appData.delayAfterQuit );
10500         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10501     }
10502     if (second.pr != NoProc) {
10503         DoSleep( appData.delayBeforeQuit );
10504         SendToProgram("quit\n", &second);
10505         DoSleep( appData.delayAfterQuit );
10506         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10507     }
10508     if (first.isr != NULL) {
10509         RemoveInputSource(first.isr);
10510     }
10511     if (second.isr != NULL) {
10512         RemoveInputSource(second.isr);
10513     }
10514
10515     ShutDownFrontEnd();
10516     exit(status);
10517 }
10518
10519 void
10520 PauseEvent()
10521 {
10522     if (appData.debugMode)
10523         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10524     if (pausing) {
10525         pausing = FALSE;
10526         ModeHighlight();
10527         if (gameMode == MachinePlaysWhite ||
10528             gameMode == MachinePlaysBlack) {
10529             StartClocks();
10530         } else {
10531             DisplayBothClocks();
10532         }
10533         if (gameMode == PlayFromGameFile) {
10534             if (appData.timeDelay >= 0) 
10535                 AutoPlayGameLoop();
10536         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10537             Reset(FALSE, TRUE);
10538             SendToICS(ics_prefix);
10539             SendToICS("refresh\n");
10540         } else if (currentMove < forwardMostMove) {
10541             ForwardInner(forwardMostMove);
10542         }
10543         pauseExamInvalid = FALSE;
10544     } else {
10545         switch (gameMode) {
10546           default:
10547             return;
10548           case IcsExamining:
10549             pauseExamForwardMostMove = forwardMostMove;
10550             pauseExamInvalid = FALSE;
10551             /* fall through */
10552           case IcsObserving:
10553           case IcsPlayingWhite:
10554           case IcsPlayingBlack:
10555             pausing = TRUE;
10556             ModeHighlight();
10557             return;
10558           case PlayFromGameFile:
10559             (void) StopLoadGameTimer();
10560             pausing = TRUE;
10561             ModeHighlight();
10562             break;
10563           case BeginningOfGame:
10564             if (appData.icsActive) return;
10565             /* else fall through */
10566           case MachinePlaysWhite:
10567           case MachinePlaysBlack:
10568           case TwoMachinesPlay:
10569             if (forwardMostMove == 0)
10570               return;           /* don't pause if no one has moved */
10571             if ((gameMode == MachinePlaysWhite &&
10572                  !WhiteOnMove(forwardMostMove)) ||
10573                 (gameMode == MachinePlaysBlack &&
10574                  WhiteOnMove(forwardMostMove))) {
10575                 StopClocks();
10576             }
10577             pausing = TRUE;
10578             ModeHighlight();
10579             break;
10580         }
10581     }
10582 }
10583
10584 void
10585 EditCommentEvent()
10586 {
10587     char title[MSG_SIZ];
10588
10589     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10590         strcpy(title, _("Edit comment"));
10591     } else {
10592         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10593                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10594                 parseList[currentMove - 1]);
10595     }
10596
10597     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10598 }
10599
10600
10601 void
10602 EditTagsEvent()
10603 {
10604     char *tags = PGNTags(&gameInfo);
10605     EditTagsPopUp(tags);
10606     free(tags);
10607 }
10608
10609 void
10610 AnalyzeModeEvent()
10611 {
10612     if (appData.noChessProgram || gameMode == AnalyzeMode)
10613       return;
10614
10615     if (gameMode != AnalyzeFile) {
10616         if (!appData.icsEngineAnalyze) {
10617                EditGameEvent();
10618                if (gameMode != EditGame) return;
10619         }
10620         ResurrectChessProgram();
10621         SendToProgram("analyze\n", &first);
10622         first.analyzing = TRUE;
10623         /*first.maybeThinking = TRUE;*/
10624         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10625         EngineOutputPopUp();
10626     }
10627     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10628     pausing = FALSE;
10629     ModeHighlight();
10630     SetGameInfo();
10631
10632     StartAnalysisClock();
10633     GetTimeMark(&lastNodeCountTime);
10634     lastNodeCount = 0;
10635 }
10636
10637 void
10638 AnalyzeFileEvent()
10639 {
10640     if (appData.noChessProgram || gameMode == AnalyzeFile)
10641       return;
10642
10643     if (gameMode != AnalyzeMode) {
10644         EditGameEvent();
10645         if (gameMode != EditGame) return;
10646         ResurrectChessProgram();
10647         SendToProgram("analyze\n", &first);
10648         first.analyzing = TRUE;
10649         /*first.maybeThinking = TRUE;*/
10650         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10651         EngineOutputPopUp();
10652     }
10653     gameMode = AnalyzeFile;
10654     pausing = FALSE;
10655     ModeHighlight();
10656     SetGameInfo();
10657
10658     StartAnalysisClock();
10659     GetTimeMark(&lastNodeCountTime);
10660     lastNodeCount = 0;
10661 }
10662
10663 void
10664 MachineWhiteEvent()
10665 {
10666     char buf[MSG_SIZ];
10667     char *bookHit = NULL;
10668
10669     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10670       return;
10671
10672
10673     if (gameMode == PlayFromGameFile || 
10674         gameMode == TwoMachinesPlay  || 
10675         gameMode == Training         || 
10676         gameMode == AnalyzeMode      || 
10677         gameMode == EndOfGame)
10678         EditGameEvent();
10679
10680     if (gameMode == EditPosition) 
10681         EditPositionDone();
10682
10683     if (!WhiteOnMove(currentMove)) {
10684         DisplayError(_("It is not White's turn"), 0);
10685         return;
10686     }
10687   
10688     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10689       ExitAnalyzeMode();
10690
10691     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10692         gameMode == AnalyzeFile)
10693         TruncateGame();
10694
10695     ResurrectChessProgram();    /* in case it isn't running */
10696     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10697         gameMode = MachinePlaysWhite;
10698         ResetClocks();
10699     } else
10700     gameMode = MachinePlaysWhite;
10701     pausing = FALSE;
10702     ModeHighlight();
10703     SetGameInfo();
10704     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10705     DisplayTitle(buf);
10706     if (first.sendName) {
10707       sprintf(buf, "name %s\n", gameInfo.black);
10708       SendToProgram(buf, &first);
10709     }
10710     if (first.sendTime) {
10711       if (first.useColors) {
10712         SendToProgram("black\n", &first); /*gnu kludge*/
10713       }
10714       SendTimeRemaining(&first, TRUE);
10715     }
10716     if (first.useColors) {
10717       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10718     }
10719     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10720     SetMachineThinkingEnables();
10721     first.maybeThinking = TRUE;
10722     StartClocks();
10723     firstMove = FALSE;
10724
10725     if (appData.autoFlipView && !flipView) {
10726       flipView = !flipView;
10727       DrawPosition(FALSE, NULL);
10728       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10729     }
10730
10731     if(bookHit) { // [HGM] book: simulate book reply
10732         static char bookMove[MSG_SIZ]; // a bit generous?
10733
10734         programStats.nodes = programStats.depth = programStats.time = 
10735         programStats.score = programStats.got_only_move = 0;
10736         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10737
10738         strcpy(bookMove, "move ");
10739         strcat(bookMove, bookHit);
10740         HandleMachineMove(bookMove, &first);
10741     }
10742 }
10743
10744 void
10745 MachineBlackEvent()
10746 {
10747     char buf[MSG_SIZ];
10748    char *bookHit = NULL;
10749
10750     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10751         return;
10752
10753
10754     if (gameMode == PlayFromGameFile || 
10755         gameMode == TwoMachinesPlay  || 
10756         gameMode == Training         || 
10757         gameMode == AnalyzeMode      || 
10758         gameMode == EndOfGame)
10759         EditGameEvent();
10760
10761     if (gameMode == EditPosition) 
10762         EditPositionDone();
10763
10764     if (WhiteOnMove(currentMove)) {
10765         DisplayError(_("It is not Black's turn"), 0);
10766         return;
10767     }
10768     
10769     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10770       ExitAnalyzeMode();
10771
10772     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10773         gameMode == AnalyzeFile)
10774         TruncateGame();
10775
10776     ResurrectChessProgram();    /* in case it isn't running */
10777     gameMode = MachinePlaysBlack;
10778     pausing = FALSE;
10779     ModeHighlight();
10780     SetGameInfo();
10781     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10782     DisplayTitle(buf);
10783     if (first.sendName) {
10784       sprintf(buf, "name %s\n", gameInfo.white);
10785       SendToProgram(buf, &first);
10786     }
10787     if (first.sendTime) {
10788       if (first.useColors) {
10789         SendToProgram("white\n", &first); /*gnu kludge*/
10790       }
10791       SendTimeRemaining(&first, FALSE);
10792     }
10793     if (first.useColors) {
10794       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10795     }
10796     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10797     SetMachineThinkingEnables();
10798     first.maybeThinking = TRUE;
10799     StartClocks();
10800
10801     if (appData.autoFlipView && flipView) {
10802       flipView = !flipView;
10803       DrawPosition(FALSE, NULL);
10804       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10805     }
10806     if(bookHit) { // [HGM] book: simulate book reply
10807         static char bookMove[MSG_SIZ]; // a bit generous?
10808
10809         programStats.nodes = programStats.depth = programStats.time = 
10810         programStats.score = programStats.got_only_move = 0;
10811         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10812
10813         strcpy(bookMove, "move ");
10814         strcat(bookMove, bookHit);
10815         HandleMachineMove(bookMove, &first);
10816     }
10817 }
10818
10819
10820 void
10821 DisplayTwoMachinesTitle()
10822 {
10823     char buf[MSG_SIZ];
10824     if (appData.matchGames > 0) {
10825         if (first.twoMachinesColor[0] == 'w') {
10826             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10827                     gameInfo.white, gameInfo.black,
10828                     first.matchWins, second.matchWins,
10829                     matchGame - 1 - (first.matchWins + second.matchWins));
10830         } else {
10831             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10832                     gameInfo.white, gameInfo.black,
10833                     second.matchWins, first.matchWins,
10834                     matchGame - 1 - (first.matchWins + second.matchWins));
10835         }
10836     } else {
10837         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10838     }
10839     DisplayTitle(buf);
10840 }
10841
10842 void
10843 TwoMachinesEvent P((void))
10844 {
10845     int i;
10846     char buf[MSG_SIZ];
10847     ChessProgramState *onmove;
10848     char *bookHit = NULL;
10849     
10850     if (appData.noChessProgram) return;
10851
10852     switch (gameMode) {
10853       case TwoMachinesPlay:
10854         return;
10855       case MachinePlaysWhite:
10856       case MachinePlaysBlack:
10857         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10858             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10859             return;
10860         }
10861         /* fall through */
10862       case BeginningOfGame:
10863       case PlayFromGameFile:
10864       case EndOfGame:
10865         EditGameEvent();
10866         if (gameMode != EditGame) return;
10867         break;
10868       case EditPosition:
10869         EditPositionDone();
10870         break;
10871       case AnalyzeMode:
10872       case AnalyzeFile:
10873         ExitAnalyzeMode();
10874         break;
10875       case EditGame:
10876       default:
10877         break;
10878     }
10879
10880     forwardMostMove = currentMove;
10881     ResurrectChessProgram();    /* in case first program isn't running */
10882
10883     if (second.pr == NULL) {
10884         StartChessProgram(&second);
10885         if (second.protocolVersion == 1) {
10886           TwoMachinesEventIfReady();
10887         } else {
10888           /* kludge: allow timeout for initial "feature" command */
10889           FreezeUI();
10890           DisplayMessage("", _("Starting second chess program"));
10891           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10892         }
10893         return;
10894     }
10895     DisplayMessage("", "");
10896     InitChessProgram(&second, FALSE);
10897     SendToProgram("force\n", &second);
10898     if (startedFromSetupPosition) {
10899         SendBoard(&second, backwardMostMove);
10900     if (appData.debugMode) {
10901         fprintf(debugFP, "Two Machines\n");
10902     }
10903     }
10904     for (i = backwardMostMove; i < forwardMostMove; i++) {
10905         SendMoveToProgram(i, &second);
10906     }
10907
10908     gameMode = TwoMachinesPlay;
10909     pausing = FALSE;
10910     ModeHighlight();
10911     SetGameInfo();
10912     DisplayTwoMachinesTitle();
10913     firstMove = TRUE;
10914     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10915         onmove = &first;
10916     } else {
10917         onmove = &second;
10918     }
10919
10920     SendToProgram(first.computerString, &first);
10921     if (first.sendName) {
10922       sprintf(buf, "name %s\n", second.tidy);
10923       SendToProgram(buf, &first);
10924     }
10925     SendToProgram(second.computerString, &second);
10926     if (second.sendName) {
10927       sprintf(buf, "name %s\n", first.tidy);
10928       SendToProgram(buf, &second);
10929     }
10930
10931     ResetClocks();
10932     if (!first.sendTime || !second.sendTime) {
10933         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10934         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10935     }
10936     if (onmove->sendTime) {
10937       if (onmove->useColors) {
10938         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10939       }
10940       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10941     }
10942     if (onmove->useColors) {
10943       SendToProgram(onmove->twoMachinesColor, onmove);
10944     }
10945     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10946 //    SendToProgram("go\n", onmove);
10947     onmove->maybeThinking = TRUE;
10948     SetMachineThinkingEnables();
10949
10950     StartClocks();
10951
10952     if(bookHit) { // [HGM] book: simulate book reply
10953         static char bookMove[MSG_SIZ]; // a bit generous?
10954
10955         programStats.nodes = programStats.depth = programStats.time = 
10956         programStats.score = programStats.got_only_move = 0;
10957         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10958
10959         strcpy(bookMove, "move ");
10960         strcat(bookMove, bookHit);
10961         savedMessage = bookMove; // args for deferred call
10962         savedState = onmove;
10963         ScheduleDelayedEvent(DeferredBookMove, 1);
10964     }
10965 }
10966
10967 void
10968 TrainingEvent()
10969 {
10970     if (gameMode == Training) {
10971       SetTrainingModeOff();
10972       gameMode = PlayFromGameFile;
10973       DisplayMessage("", _("Training mode off"));
10974     } else {
10975       gameMode = Training;
10976       animateTraining = appData.animate;
10977
10978       /* make sure we are not already at the end of the game */
10979       if (currentMove < forwardMostMove) {
10980         SetTrainingModeOn();
10981         DisplayMessage("", _("Training mode on"));
10982       } else {
10983         gameMode = PlayFromGameFile;
10984         DisplayError(_("Already at end of game"), 0);
10985       }
10986     }
10987     ModeHighlight();
10988 }
10989
10990 void
10991 IcsClientEvent()
10992 {
10993     if (!appData.icsActive) return;
10994     switch (gameMode) {
10995       case IcsPlayingWhite:
10996       case IcsPlayingBlack:
10997       case IcsObserving:
10998       case IcsIdle:
10999       case BeginningOfGame:
11000       case IcsExamining:
11001         return;
11002
11003       case EditGame:
11004         break;
11005
11006       case EditPosition:
11007         EditPositionDone();
11008         break;
11009
11010       case AnalyzeMode:
11011       case AnalyzeFile:
11012         ExitAnalyzeMode();
11013         break;
11014         
11015       default:
11016         EditGameEvent();
11017         break;
11018     }
11019
11020     gameMode = IcsIdle;
11021     ModeHighlight();
11022     return;
11023 }
11024
11025
11026 void
11027 EditGameEvent()
11028 {
11029     int i;
11030
11031     switch (gameMode) {
11032       case Training:
11033         SetTrainingModeOff();
11034         break;
11035       case MachinePlaysWhite:
11036       case MachinePlaysBlack:
11037       case BeginningOfGame:
11038         SendToProgram("force\n", &first);
11039         SetUserThinkingEnables();
11040         break;
11041       case PlayFromGameFile:
11042         (void) StopLoadGameTimer();
11043         if (gameFileFP != NULL) {
11044             gameFileFP = NULL;
11045         }
11046         break;
11047       case EditPosition:
11048         EditPositionDone();
11049         break;
11050       case AnalyzeMode:
11051       case AnalyzeFile:
11052         ExitAnalyzeMode();
11053         SendToProgram("force\n", &first);
11054         break;
11055       case TwoMachinesPlay:
11056         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11057         ResurrectChessProgram();
11058         SetUserThinkingEnables();
11059         break;
11060       case EndOfGame:
11061         ResurrectChessProgram();
11062         break;
11063       case IcsPlayingBlack:
11064       case IcsPlayingWhite:
11065         DisplayError(_("Warning: You are still playing a game"), 0);
11066         break;
11067       case IcsObserving:
11068         DisplayError(_("Warning: You are still observing a game"), 0);
11069         break;
11070       case IcsExamining:
11071         DisplayError(_("Warning: You are still examining a game"), 0);
11072         break;
11073       case IcsIdle:
11074         break;
11075       case EditGame:
11076       default:
11077         return;
11078     }
11079     
11080     pausing = FALSE;
11081     StopClocks();
11082     first.offeredDraw = second.offeredDraw = 0;
11083
11084     if (gameMode == PlayFromGameFile) {
11085         whiteTimeRemaining = timeRemaining[0][currentMove];
11086         blackTimeRemaining = timeRemaining[1][currentMove];
11087         DisplayTitle("");
11088     }
11089
11090     if (gameMode == MachinePlaysWhite ||
11091         gameMode == MachinePlaysBlack ||
11092         gameMode == TwoMachinesPlay ||
11093         gameMode == EndOfGame) {
11094         i = forwardMostMove;
11095         while (i > currentMove) {
11096             SendToProgram("undo\n", &first);
11097             i--;
11098         }
11099         whiteTimeRemaining = timeRemaining[0][currentMove];
11100         blackTimeRemaining = timeRemaining[1][currentMove];
11101         DisplayBothClocks();
11102         if (whiteFlag || blackFlag) {
11103             whiteFlag = blackFlag = 0;
11104         }
11105         DisplayTitle("");
11106     }           
11107     
11108     gameMode = EditGame;
11109     ModeHighlight();
11110     SetGameInfo();
11111 }
11112
11113
11114 void
11115 EditPositionEvent()
11116 {
11117     if (gameMode == EditPosition) {
11118         EditGameEvent();
11119         return;
11120     }
11121     
11122     EditGameEvent();
11123     if (gameMode != EditGame) return;
11124     
11125     gameMode = EditPosition;
11126     ModeHighlight();
11127     SetGameInfo();
11128     if (currentMove > 0)
11129       CopyBoard(boards[0], boards[currentMove]);
11130     
11131     blackPlaysFirst = !WhiteOnMove(currentMove);
11132     ResetClocks();
11133     currentMove = forwardMostMove = backwardMostMove = 0;
11134     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11135     DisplayMove(-1);
11136 }
11137
11138 void
11139 ExitAnalyzeMode()
11140 {
11141     /* [DM] icsEngineAnalyze - possible call from other functions */
11142     if (appData.icsEngineAnalyze) {
11143         appData.icsEngineAnalyze = FALSE;
11144
11145         DisplayMessage("",_("Close ICS engine analyze..."));
11146     }
11147     if (first.analysisSupport && first.analyzing) {
11148       SendToProgram("exit\n", &first);
11149       first.analyzing = FALSE;
11150     }
11151     thinkOutput[0] = NULLCHAR;
11152 }
11153
11154 void
11155 EditPositionDone()
11156 {
11157     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11158
11159     startedFromSetupPosition = TRUE;
11160     InitChessProgram(&first, FALSE);
11161     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11162     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11163         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11164         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11165     } else castlingRights[0][2] = -1;
11166     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11167         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11168         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11169     } else castlingRights[0][5] = -1;
11170     SendToProgram("force\n", &first);
11171     if (blackPlaysFirst) {
11172         strcpy(moveList[0], "");
11173         strcpy(parseList[0], "");
11174         currentMove = forwardMostMove = backwardMostMove = 1;
11175         CopyBoard(boards[1], boards[0]);
11176         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11177         { int i;
11178           epStatus[1] = epStatus[0];
11179           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11180         }
11181     } else {
11182         currentMove = forwardMostMove = backwardMostMove = 0;
11183     }
11184     SendBoard(&first, forwardMostMove);
11185     if (appData.debugMode) {
11186         fprintf(debugFP, "EditPosDone\n");
11187     }
11188     DisplayTitle("");
11189     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11190     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11191     gameMode = EditGame;
11192     ModeHighlight();
11193     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11194     ClearHighlights(); /* [AS] */
11195 }
11196
11197 /* Pause for `ms' milliseconds */
11198 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11199 void
11200 TimeDelay(ms)
11201      long ms;
11202 {
11203     TimeMark m1, m2;
11204
11205     GetTimeMark(&m1);
11206     do {
11207         GetTimeMark(&m2);
11208     } while (SubtractTimeMarks(&m2, &m1) < ms);
11209 }
11210
11211 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11212 void
11213 SendMultiLineToICS(buf)
11214      char *buf;
11215 {
11216     char temp[MSG_SIZ+1], *p;
11217     int len;
11218
11219     len = strlen(buf);
11220     if (len > MSG_SIZ)
11221       len = MSG_SIZ;
11222   
11223     strncpy(temp, buf, len);
11224     temp[len] = 0;
11225
11226     p = temp;
11227     while (*p) {
11228         if (*p == '\n' || *p == '\r')
11229           *p = ' ';
11230         ++p;
11231     }
11232
11233     strcat(temp, "\n");
11234     SendToICS(temp);
11235     SendToPlayer(temp, strlen(temp));
11236 }
11237
11238 void
11239 SetWhiteToPlayEvent()
11240 {
11241     if (gameMode == EditPosition) {
11242         blackPlaysFirst = FALSE;
11243         DisplayBothClocks();    /* works because currentMove is 0 */
11244     } else if (gameMode == IcsExamining) {
11245         SendToICS(ics_prefix);
11246         SendToICS("tomove white\n");
11247     }
11248 }
11249
11250 void
11251 SetBlackToPlayEvent()
11252 {
11253     if (gameMode == EditPosition) {
11254         blackPlaysFirst = TRUE;
11255         currentMove = 1;        /* kludge */
11256         DisplayBothClocks();
11257         currentMove = 0;
11258     } else if (gameMode == IcsExamining) {
11259         SendToICS(ics_prefix);
11260         SendToICS("tomove black\n");
11261     }
11262 }
11263
11264 void
11265 EditPositionMenuEvent(selection, x, y)
11266      ChessSquare selection;
11267      int x, y;
11268 {
11269     char buf[MSG_SIZ];
11270     ChessSquare piece = boards[0][y][x];
11271
11272     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11273
11274     switch (selection) {
11275       case ClearBoard:
11276         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11277             SendToICS(ics_prefix);
11278             SendToICS("bsetup clear\n");
11279         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11280             SendToICS(ics_prefix);
11281             SendToICS("clearboard\n");
11282         } else {
11283             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11284                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11285                 for (y = 0; y < BOARD_HEIGHT; y++) {
11286                     if (gameMode == IcsExamining) {
11287                         if (boards[currentMove][y][x] != EmptySquare) {
11288                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11289                                     AAA + x, ONE + y);
11290                             SendToICS(buf);
11291                         }
11292                     } else {
11293                         boards[0][y][x] = p;
11294                     }
11295                 }
11296             }
11297         }
11298         if (gameMode == EditPosition) {
11299             DrawPosition(FALSE, boards[0]);
11300         }
11301         break;
11302
11303       case WhitePlay:
11304         SetWhiteToPlayEvent();
11305         break;
11306
11307       case BlackPlay:
11308         SetBlackToPlayEvent();
11309         break;
11310
11311       case EmptySquare:
11312         if (gameMode == IcsExamining) {
11313             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11314             SendToICS(buf);
11315         } else {
11316             boards[0][y][x] = EmptySquare;
11317             DrawPosition(FALSE, boards[0]);
11318         }
11319         break;
11320
11321       case PromotePiece:
11322         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11323            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11324             selection = (ChessSquare) (PROMOTED piece);
11325         } else if(piece == EmptySquare) selection = WhiteSilver;
11326         else selection = (ChessSquare)((int)piece - 1);
11327         goto defaultlabel;
11328
11329       case DemotePiece:
11330         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11331            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11332             selection = (ChessSquare) (DEMOTED piece);
11333         } else if(piece == EmptySquare) selection = BlackSilver;
11334         else selection = (ChessSquare)((int)piece + 1);       
11335         goto defaultlabel;
11336
11337       case WhiteQueen:
11338       case BlackQueen:
11339         if(gameInfo.variant == VariantShatranj ||
11340            gameInfo.variant == VariantXiangqi  ||
11341            gameInfo.variant == VariantCourier    )
11342             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11343         goto defaultlabel;
11344
11345       case WhiteKing:
11346       case BlackKing:
11347         if(gameInfo.variant == VariantXiangqi)
11348             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11349         if(gameInfo.variant == VariantKnightmate)
11350             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11351       default:
11352         defaultlabel:
11353         if (gameMode == IcsExamining) {
11354             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11355                     PieceToChar(selection), AAA + x, ONE + y);
11356             SendToICS(buf);
11357         } else {
11358             boards[0][y][x] = selection;
11359             DrawPosition(FALSE, boards[0]);
11360         }
11361         break;
11362     }
11363 }
11364
11365
11366 void
11367 DropMenuEvent(selection, x, y)
11368      ChessSquare selection;
11369      int x, y;
11370 {
11371     ChessMove moveType;
11372
11373     switch (gameMode) {
11374       case IcsPlayingWhite:
11375       case MachinePlaysBlack:
11376         if (!WhiteOnMove(currentMove)) {
11377             DisplayMoveError(_("It is Black's turn"));
11378             return;
11379         }
11380         moveType = WhiteDrop;
11381         break;
11382       case IcsPlayingBlack:
11383       case MachinePlaysWhite:
11384         if (WhiteOnMove(currentMove)) {
11385             DisplayMoveError(_("It is White's turn"));
11386             return;
11387         }
11388         moveType = BlackDrop;
11389         break;
11390       case EditGame:
11391         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11392         break;
11393       default:
11394         return;
11395     }
11396
11397     if (moveType == BlackDrop && selection < BlackPawn) {
11398       selection = (ChessSquare) ((int) selection
11399                                  + (int) BlackPawn - (int) WhitePawn);
11400     }
11401     if (boards[currentMove][y][x] != EmptySquare) {
11402         DisplayMoveError(_("That square is occupied"));
11403         return;
11404     }
11405
11406     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11407 }
11408
11409 void
11410 AcceptEvent()
11411 {
11412     /* Accept a pending offer of any kind from opponent */
11413     
11414     if (appData.icsActive) {
11415         SendToICS(ics_prefix);
11416         SendToICS("accept\n");
11417     } else if (cmailMsgLoaded) {
11418         if (currentMove == cmailOldMove &&
11419             commentList[cmailOldMove] != NULL &&
11420             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11421                    "Black offers a draw" : "White offers a draw")) {
11422             TruncateGame();
11423             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11424             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11425         } else {
11426             DisplayError(_("There is no pending offer on this move"), 0);
11427             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11428         }
11429     } else {
11430         /* Not used for offers from chess program */
11431     }
11432 }
11433
11434 void
11435 DeclineEvent()
11436 {
11437     /* Decline a pending offer of any kind from opponent */
11438     
11439     if (appData.icsActive) {
11440         SendToICS(ics_prefix);
11441         SendToICS("decline\n");
11442     } else if (cmailMsgLoaded) {
11443         if (currentMove == cmailOldMove &&
11444             commentList[cmailOldMove] != NULL &&
11445             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11446                    "Black offers a draw" : "White offers a draw")) {
11447 #ifdef NOTDEF
11448             AppendComment(cmailOldMove, "Draw declined");
11449             DisplayComment(cmailOldMove - 1, "Draw declined");
11450 #endif /*NOTDEF*/
11451         } else {
11452             DisplayError(_("There is no pending offer on this move"), 0);
11453         }
11454     } else {
11455         /* Not used for offers from chess program */
11456     }
11457 }
11458
11459 void
11460 RematchEvent()
11461 {
11462     /* Issue ICS rematch command */
11463     if (appData.icsActive) {
11464         SendToICS(ics_prefix);
11465         SendToICS("rematch\n");
11466     }
11467 }
11468
11469 void
11470 CallFlagEvent()
11471 {
11472     /* Call your opponent's flag (claim a win on time) */
11473     if (appData.icsActive) {
11474         SendToICS(ics_prefix);
11475         SendToICS("flag\n");
11476     } else {
11477         switch (gameMode) {
11478           default:
11479             return;
11480           case MachinePlaysWhite:
11481             if (whiteFlag) {
11482                 if (blackFlag)
11483                   GameEnds(GameIsDrawn, "Both players ran out of time",
11484                            GE_PLAYER);
11485                 else
11486                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11487             } else {
11488                 DisplayError(_("Your opponent is not out of time"), 0);
11489             }
11490             break;
11491           case MachinePlaysBlack:
11492             if (blackFlag) {
11493                 if (whiteFlag)
11494                   GameEnds(GameIsDrawn, "Both players ran out of time",
11495                            GE_PLAYER);
11496                 else
11497                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11498             } else {
11499                 DisplayError(_("Your opponent is not out of time"), 0);
11500             }
11501             break;
11502         }
11503     }
11504 }
11505
11506 void
11507 DrawEvent()
11508 {
11509     /* Offer draw or accept pending draw offer from opponent */
11510     
11511     if (appData.icsActive) {
11512         /* Note: tournament rules require draw offers to be
11513            made after you make your move but before you punch
11514            your clock.  Currently ICS doesn't let you do that;
11515            instead, you immediately punch your clock after making
11516            a move, but you can offer a draw at any time. */
11517         
11518         SendToICS(ics_prefix);
11519         SendToICS("draw\n");
11520     } else if (cmailMsgLoaded) {
11521         if (currentMove == cmailOldMove &&
11522             commentList[cmailOldMove] != NULL &&
11523             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11524                    "Black offers a draw" : "White offers a draw")) {
11525             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11527         } else if (currentMove == cmailOldMove + 1) {
11528             char *offer = WhiteOnMove(cmailOldMove) ?
11529               "White offers a draw" : "Black offers a draw";
11530             AppendComment(currentMove, offer);
11531             DisplayComment(currentMove - 1, offer);
11532             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11533         } else {
11534             DisplayError(_("You must make your move before offering a draw"), 0);
11535             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11536         }
11537     } else if (first.offeredDraw) {
11538         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11539     } else {
11540         if (first.sendDrawOffers) {
11541             SendToProgram("draw\n", &first);
11542             userOfferedDraw = TRUE;
11543         }
11544     }
11545 }
11546
11547 void
11548 AdjournEvent()
11549 {
11550     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11551     
11552     if (appData.icsActive) {
11553         SendToICS(ics_prefix);
11554         SendToICS("adjourn\n");
11555     } else {
11556         /* Currently GNU Chess doesn't offer or accept Adjourns */
11557     }
11558 }
11559
11560
11561 void
11562 AbortEvent()
11563 {
11564     /* Offer Abort or accept pending Abort offer from opponent */
11565     
11566     if (appData.icsActive) {
11567         SendToICS(ics_prefix);
11568         SendToICS("abort\n");
11569     } else {
11570         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11571     }
11572 }
11573
11574 void
11575 ResignEvent()
11576 {
11577     /* Resign.  You can do this even if it's not your turn. */
11578     
11579     if (appData.icsActive) {
11580         SendToICS(ics_prefix);
11581         SendToICS("resign\n");
11582     } else {
11583         switch (gameMode) {
11584           case MachinePlaysWhite:
11585             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11586             break;
11587           case MachinePlaysBlack:
11588             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11589             break;
11590           case EditGame:
11591             if (cmailMsgLoaded) {
11592                 TruncateGame();
11593                 if (WhiteOnMove(cmailOldMove)) {
11594                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11595                 } else {
11596                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11597                 }
11598                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11599             }
11600             break;
11601           default:
11602             break;
11603         }
11604     }
11605 }
11606
11607
11608 void
11609 StopObservingEvent()
11610 {
11611     /* Stop observing current games */
11612     SendToICS(ics_prefix);
11613     SendToICS("unobserve\n");
11614 }
11615
11616 void
11617 StopExaminingEvent()
11618 {
11619     /* Stop observing current game */
11620     SendToICS(ics_prefix);
11621     SendToICS("unexamine\n");
11622 }
11623
11624 void
11625 ForwardInner(target)
11626      int target;
11627 {
11628     int limit;
11629
11630     if (appData.debugMode)
11631         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11632                 target, currentMove, forwardMostMove);
11633
11634     if (gameMode == EditPosition)
11635       return;
11636
11637     if (gameMode == PlayFromGameFile && !pausing)
11638       PauseEvent();
11639     
11640     if (gameMode == IcsExamining && pausing)
11641       limit = pauseExamForwardMostMove;
11642     else
11643       limit = forwardMostMove;
11644     
11645     if (target > limit) target = limit;
11646
11647     if (target > 0 && moveList[target - 1][0]) {
11648         int fromX, fromY, toX, toY;
11649         toX = moveList[target - 1][2] - AAA;
11650         toY = moveList[target - 1][3] - ONE;
11651         if (moveList[target - 1][1] == '@') {
11652             if (appData.highlightLastMove) {
11653                 SetHighlights(-1, -1, toX, toY);
11654             }
11655         } else {
11656             fromX = moveList[target - 1][0] - AAA;
11657             fromY = moveList[target - 1][1] - ONE;
11658             if (target == currentMove + 1) {
11659                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11660             }
11661             if (appData.highlightLastMove) {
11662                 SetHighlights(fromX, fromY, toX, toY);
11663             }
11664         }
11665     }
11666     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11667         gameMode == Training || gameMode == PlayFromGameFile || 
11668         gameMode == AnalyzeFile) {
11669         while (currentMove < target) {
11670             SendMoveToProgram(currentMove++, &first);
11671         }
11672     } else {
11673         currentMove = target;
11674     }
11675     
11676     if (gameMode == EditGame || gameMode == EndOfGame) {
11677         whiteTimeRemaining = timeRemaining[0][currentMove];
11678         blackTimeRemaining = timeRemaining[1][currentMove];
11679     }
11680     DisplayBothClocks();
11681     DisplayMove(currentMove - 1);
11682     DrawPosition(FALSE, boards[currentMove]);
11683     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11684     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11685         DisplayComment(currentMove - 1, commentList[currentMove]);
11686     }
11687 }
11688
11689
11690 void
11691 ForwardEvent()
11692 {
11693     if (gameMode == IcsExamining && !pausing) {
11694         SendToICS(ics_prefix);
11695         SendToICS("forward\n");
11696     } else {
11697         ForwardInner(currentMove + 1);
11698     }
11699 }
11700
11701 void
11702 ToEndEvent()
11703 {
11704     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11705         /* to optimze, we temporarily turn off analysis mode while we feed
11706          * the remaining moves to the engine. Otherwise we get analysis output
11707          * after each move.
11708          */ 
11709         if (first.analysisSupport) {
11710           SendToProgram("exit\nforce\n", &first);
11711           first.analyzing = FALSE;
11712         }
11713     }
11714         
11715     if (gameMode == IcsExamining && !pausing) {
11716         SendToICS(ics_prefix);
11717         SendToICS("forward 999999\n");
11718     } else {
11719         ForwardInner(forwardMostMove);
11720     }
11721
11722     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11723         /* we have fed all the moves, so reactivate analysis mode */
11724         SendToProgram("analyze\n", &first);
11725         first.analyzing = TRUE;
11726         /*first.maybeThinking = TRUE;*/
11727         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11728     }
11729 }
11730
11731 void
11732 BackwardInner(target)
11733      int target;
11734 {
11735     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11736
11737     if (appData.debugMode)
11738         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11739                 target, currentMove, forwardMostMove);
11740
11741     if (gameMode == EditPosition) return;
11742     if (currentMove <= backwardMostMove) {
11743         ClearHighlights();
11744         DrawPosition(full_redraw, boards[currentMove]);
11745         return;
11746     }
11747     if (gameMode == PlayFromGameFile && !pausing)
11748       PauseEvent();
11749     
11750     if (moveList[target][0]) {
11751         int fromX, fromY, toX, toY;
11752         toX = moveList[target][2] - AAA;
11753         toY = moveList[target][3] - ONE;
11754         if (moveList[target][1] == '@') {
11755             if (appData.highlightLastMove) {
11756                 SetHighlights(-1, -1, toX, toY);
11757             }
11758         } else {
11759             fromX = moveList[target][0] - AAA;
11760             fromY = moveList[target][1] - ONE;
11761             if (target == currentMove - 1) {
11762                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11763             }
11764             if (appData.highlightLastMove) {
11765                 SetHighlights(fromX, fromY, toX, toY);
11766             }
11767         }
11768     }
11769     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11770         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11771         while (currentMove > target) {
11772             SendToProgram("undo\n", &first);
11773             currentMove--;
11774         }
11775     } else {
11776         currentMove = target;
11777     }
11778     
11779     if (gameMode == EditGame || gameMode == EndOfGame) {
11780         whiteTimeRemaining = timeRemaining[0][currentMove];
11781         blackTimeRemaining = timeRemaining[1][currentMove];
11782     }
11783     DisplayBothClocks();
11784     DisplayMove(currentMove - 1);
11785     DrawPosition(full_redraw, boards[currentMove]);
11786     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11787     // [HGM] PV info: routine tests if comment empty
11788     DisplayComment(currentMove - 1, commentList[currentMove]);
11789 }
11790
11791 void
11792 BackwardEvent()
11793 {
11794     if (gameMode == IcsExamining && !pausing) {
11795         SendToICS(ics_prefix);
11796         SendToICS("backward\n");
11797     } else {
11798         BackwardInner(currentMove - 1);
11799     }
11800 }
11801
11802 void
11803 ToStartEvent()
11804 {
11805     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11806         /* to optimze, we temporarily turn off analysis mode while we undo
11807          * all the moves. Otherwise we get analysis output after each undo.
11808          */ 
11809         if (first.analysisSupport) {
11810           SendToProgram("exit\nforce\n", &first);
11811           first.analyzing = FALSE;
11812         }
11813     }
11814
11815     if (gameMode == IcsExamining && !pausing) {
11816         SendToICS(ics_prefix);
11817         SendToICS("backward 999999\n");
11818     } else {
11819         BackwardInner(backwardMostMove);
11820     }
11821
11822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11823         /* we have fed all the moves, so reactivate analysis mode */
11824         SendToProgram("analyze\n", &first);
11825         first.analyzing = TRUE;
11826         /*first.maybeThinking = TRUE;*/
11827         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11828     }
11829 }
11830
11831 void
11832 ToNrEvent(int to)
11833 {
11834   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11835   if (to >= forwardMostMove) to = forwardMostMove;
11836   if (to <= backwardMostMove) to = backwardMostMove;
11837   if (to < currentMove) {
11838     BackwardInner(to);
11839   } else {
11840     ForwardInner(to);
11841   }
11842 }
11843
11844 void
11845 RevertEvent()
11846 {
11847     if (gameMode != IcsExamining) {
11848         DisplayError(_("You are not examining a game"), 0);
11849         return;
11850     }
11851     if (pausing) {
11852         DisplayError(_("You can't revert while pausing"), 0);
11853         return;
11854     }
11855     SendToICS(ics_prefix);
11856     SendToICS("revert\n");
11857 }
11858
11859 void
11860 RetractMoveEvent()
11861 {
11862     switch (gameMode) {
11863       case MachinePlaysWhite:
11864       case MachinePlaysBlack:
11865         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11866             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11867             return;
11868         }
11869         if (forwardMostMove < 2) return;
11870         currentMove = forwardMostMove = forwardMostMove - 2;
11871         whiteTimeRemaining = timeRemaining[0][currentMove];
11872         blackTimeRemaining = timeRemaining[1][currentMove];
11873         DisplayBothClocks();
11874         DisplayMove(currentMove - 1);
11875         ClearHighlights();/*!! could figure this out*/
11876         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11877         SendToProgram("remove\n", &first);
11878         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11879         break;
11880
11881       case BeginningOfGame:
11882       default:
11883         break;
11884
11885       case IcsPlayingWhite:
11886       case IcsPlayingBlack:
11887         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11888             SendToICS(ics_prefix);
11889             SendToICS("takeback 2\n");
11890         } else {
11891             SendToICS(ics_prefix);
11892             SendToICS("takeback 1\n");
11893         }
11894         break;
11895     }
11896 }
11897
11898 void
11899 MoveNowEvent()
11900 {
11901     ChessProgramState *cps;
11902
11903     switch (gameMode) {
11904       case MachinePlaysWhite:
11905         if (!WhiteOnMove(forwardMostMove)) {
11906             DisplayError(_("It is your turn"), 0);
11907             return;
11908         }
11909         cps = &first;
11910         break;
11911       case MachinePlaysBlack:
11912         if (WhiteOnMove(forwardMostMove)) {
11913             DisplayError(_("It is your turn"), 0);
11914             return;
11915         }
11916         cps = &first;
11917         break;
11918       case TwoMachinesPlay:
11919         if (WhiteOnMove(forwardMostMove) ==
11920             (first.twoMachinesColor[0] == 'w')) {
11921             cps = &first;
11922         } else {
11923             cps = &second;
11924         }
11925         break;
11926       case BeginningOfGame:
11927       default:
11928         return;
11929     }
11930     SendToProgram("?\n", cps);
11931 }
11932
11933 void
11934 TruncateGameEvent()
11935 {
11936     EditGameEvent();
11937     if (gameMode != EditGame) return;
11938     TruncateGame();
11939 }
11940
11941 void
11942 TruncateGame()
11943 {
11944     if (forwardMostMove > currentMove) {
11945         if (gameInfo.resultDetails != NULL) {
11946             free(gameInfo.resultDetails);
11947             gameInfo.resultDetails = NULL;
11948             gameInfo.result = GameUnfinished;
11949         }
11950         forwardMostMove = currentMove;
11951         HistorySet(parseList, backwardMostMove, forwardMostMove,
11952                    currentMove-1);
11953     }
11954 }
11955
11956 void
11957 HintEvent()
11958 {
11959     if (appData.noChessProgram) return;
11960     switch (gameMode) {
11961       case MachinePlaysWhite:
11962         if (WhiteOnMove(forwardMostMove)) {
11963             DisplayError(_("Wait until your turn"), 0);
11964             return;
11965         }
11966         break;
11967       case BeginningOfGame:
11968       case MachinePlaysBlack:
11969         if (!WhiteOnMove(forwardMostMove)) {
11970             DisplayError(_("Wait until your turn"), 0);
11971             return;
11972         }
11973         break;
11974       default:
11975         DisplayError(_("No hint available"), 0);
11976         return;
11977     }
11978     SendToProgram("hint\n", &first);
11979     hintRequested = TRUE;
11980 }
11981
11982 void
11983 BookEvent()
11984 {
11985     if (appData.noChessProgram) return;
11986     switch (gameMode) {
11987       case MachinePlaysWhite:
11988         if (WhiteOnMove(forwardMostMove)) {
11989             DisplayError(_("Wait until your turn"), 0);
11990             return;
11991         }
11992         break;
11993       case BeginningOfGame:
11994       case MachinePlaysBlack:
11995         if (!WhiteOnMove(forwardMostMove)) {
11996             DisplayError(_("Wait until your turn"), 0);
11997             return;
11998         }
11999         break;
12000       case EditPosition:
12001         EditPositionDone();
12002         break;
12003       case TwoMachinesPlay:
12004         return;
12005       default:
12006         break;
12007     }
12008     SendToProgram("bk\n", &first);
12009     bookOutput[0] = NULLCHAR;
12010     bookRequested = TRUE;
12011 }
12012
12013 void
12014 AboutGameEvent()
12015 {
12016     char *tags = PGNTags(&gameInfo);
12017     TagsPopUp(tags, CmailMsg());
12018     free(tags);
12019 }
12020
12021 /* end button procedures */
12022
12023 void
12024 PrintPosition(fp, move)
12025      FILE *fp;
12026      int move;
12027 {
12028     int i, j;
12029     
12030     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12031         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12032             char c = PieceToChar(boards[move][i][j]);
12033             fputc(c == 'x' ? '.' : c, fp);
12034             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12035         }
12036     }
12037     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12038       fprintf(fp, "white to play\n");
12039     else
12040       fprintf(fp, "black to play\n");
12041 }
12042
12043 void
12044 PrintOpponents(fp)
12045      FILE *fp;
12046 {
12047     if (gameInfo.white != NULL) {
12048         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12049     } else {
12050         fprintf(fp, "\n");
12051     }
12052 }
12053
12054 /* Find last component of program's own name, using some heuristics */
12055 void
12056 TidyProgramName(prog, host, buf)
12057      char *prog, *host, buf[MSG_SIZ];
12058 {
12059     char *p, *q;
12060     int local = (strcmp(host, "localhost") == 0);
12061     while (!local && (p = strchr(prog, ';')) != NULL) {
12062         p++;
12063         while (*p == ' ') p++;
12064         prog = p;
12065     }
12066     if (*prog == '"' || *prog == '\'') {
12067         q = strchr(prog + 1, *prog);
12068     } else {
12069         q = strchr(prog, ' ');
12070     }
12071     if (q == NULL) q = prog + strlen(prog);
12072     p = q;
12073     while (p >= prog && *p != '/' && *p != '\\') p--;
12074     p++;
12075     if(p == prog && *p == '"') p++;
12076     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12077     memcpy(buf, p, q - p);
12078     buf[q - p] = NULLCHAR;
12079     if (!local) {
12080         strcat(buf, "@");
12081         strcat(buf, host);
12082     }
12083 }
12084
12085 char *
12086 TimeControlTagValue()
12087 {
12088     char buf[MSG_SIZ];
12089     if (!appData.clockMode) {
12090         strcpy(buf, "-");
12091     } else if (movesPerSession > 0) {
12092         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12093     } else if (timeIncrement == 0) {
12094         sprintf(buf, "%ld", timeControl/1000);
12095     } else {
12096         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12097     }
12098     return StrSave(buf);
12099 }
12100
12101 void
12102 SetGameInfo()
12103 {
12104     /* This routine is used only for certain modes */
12105     VariantClass v = gameInfo.variant;
12106     ClearGameInfo(&gameInfo);
12107     gameInfo.variant = v;
12108
12109     switch (gameMode) {
12110       case MachinePlaysWhite:
12111         gameInfo.event = StrSave( appData.pgnEventHeader );
12112         gameInfo.site = StrSave(HostName());
12113         gameInfo.date = PGNDate();
12114         gameInfo.round = StrSave("-");
12115         gameInfo.white = StrSave(first.tidy);
12116         gameInfo.black = StrSave(UserName());
12117         gameInfo.timeControl = TimeControlTagValue();
12118         break;
12119
12120       case MachinePlaysBlack:
12121         gameInfo.event = StrSave( appData.pgnEventHeader );
12122         gameInfo.site = StrSave(HostName());
12123         gameInfo.date = PGNDate();
12124         gameInfo.round = StrSave("-");
12125         gameInfo.white = StrSave(UserName());
12126         gameInfo.black = StrSave(first.tidy);
12127         gameInfo.timeControl = TimeControlTagValue();
12128         break;
12129
12130       case TwoMachinesPlay:
12131         gameInfo.event = StrSave( appData.pgnEventHeader );
12132         gameInfo.site = StrSave(HostName());
12133         gameInfo.date = PGNDate();
12134         if (matchGame > 0) {
12135             char buf[MSG_SIZ];
12136             sprintf(buf, "%d", matchGame);
12137             gameInfo.round = StrSave(buf);
12138         } else {
12139             gameInfo.round = StrSave("-");
12140         }
12141         if (first.twoMachinesColor[0] == 'w') {
12142             gameInfo.white = StrSave(first.tidy);
12143             gameInfo.black = StrSave(second.tidy);
12144         } else {
12145             gameInfo.white = StrSave(second.tidy);
12146             gameInfo.black = StrSave(first.tidy);
12147         }
12148         gameInfo.timeControl = TimeControlTagValue();
12149         break;
12150
12151       case EditGame:
12152         gameInfo.event = StrSave("Edited game");
12153         gameInfo.site = StrSave(HostName());
12154         gameInfo.date = PGNDate();
12155         gameInfo.round = StrSave("-");
12156         gameInfo.white = StrSave("-");
12157         gameInfo.black = StrSave("-");
12158         break;
12159
12160       case EditPosition:
12161         gameInfo.event = StrSave("Edited position");
12162         gameInfo.site = StrSave(HostName());
12163         gameInfo.date = PGNDate();
12164         gameInfo.round = StrSave("-");
12165         gameInfo.white = StrSave("-");
12166         gameInfo.black = StrSave("-");
12167         break;
12168
12169       case IcsPlayingWhite:
12170       case IcsPlayingBlack:
12171       case IcsObserving:
12172       case IcsExamining:
12173         break;
12174
12175       case PlayFromGameFile:
12176         gameInfo.event = StrSave("Game from non-PGN file");
12177         gameInfo.site = StrSave(HostName());
12178         gameInfo.date = PGNDate();
12179         gameInfo.round = StrSave("-");
12180         gameInfo.white = StrSave("?");
12181         gameInfo.black = StrSave("?");
12182         break;
12183
12184       default:
12185         break;
12186     }
12187 }
12188
12189 void
12190 ReplaceComment(index, text)
12191      int index;
12192      char *text;
12193 {
12194     int len;
12195
12196     while (*text == '\n') text++;
12197     len = strlen(text);
12198     while (len > 0 && text[len - 1] == '\n') len--;
12199
12200     if (commentList[index] != NULL)
12201       free(commentList[index]);
12202
12203     if (len == 0) {
12204         commentList[index] = NULL;
12205         return;
12206     }
12207     commentList[index] = (char *) malloc(len + 2);
12208     strncpy(commentList[index], text, len);
12209     commentList[index][len] = '\n';
12210     commentList[index][len + 1] = NULLCHAR;
12211 }
12212
12213 void
12214 CrushCRs(text)
12215      char *text;
12216 {
12217   char *p = text;
12218   char *q = text;
12219   char ch;
12220
12221   do {
12222     ch = *p++;
12223     if (ch == '\r') continue;
12224     *q++ = ch;
12225   } while (ch != '\0');
12226 }
12227
12228 void
12229 AppendComment(index, text)
12230      int index;
12231      char *text;
12232 {
12233     int oldlen, len;
12234     char *old;
12235
12236     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12237
12238     CrushCRs(text);
12239     while (*text == '\n') text++;
12240     len = strlen(text);
12241     while (len > 0 && text[len - 1] == '\n') len--;
12242
12243     if (len == 0) return;
12244
12245     if (commentList[index] != NULL) {
12246         old = commentList[index];
12247         oldlen = strlen(old);
12248         commentList[index] = (char *) malloc(oldlen + len + 2);
12249         strcpy(commentList[index], old);
12250         free(old);
12251         strncpy(&commentList[index][oldlen], text, len);
12252         commentList[index][oldlen + len] = '\n';
12253         commentList[index][oldlen + len + 1] = NULLCHAR;
12254     } else {
12255         commentList[index] = (char *) malloc(len + 2);
12256         strncpy(commentList[index], text, len);
12257         commentList[index][len] = '\n';
12258         commentList[index][len + 1] = NULLCHAR;
12259     }
12260 }
12261
12262 static char * FindStr( char * text, char * sub_text )
12263 {
12264     char * result = strstr( text, sub_text );
12265
12266     if( result != NULL ) {
12267         result += strlen( sub_text );
12268     }
12269
12270     return result;
12271 }
12272
12273 /* [AS] Try to extract PV info from PGN comment */
12274 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12275 char *GetInfoFromComment( int index, char * text )
12276 {
12277     char * sep = text;
12278
12279     if( text != NULL && index > 0 ) {
12280         int score = 0;
12281         int depth = 0;
12282         int time = -1, sec = 0, deci;
12283         char * s_eval = FindStr( text, "[%eval " );
12284         char * s_emt = FindStr( text, "[%emt " );
12285
12286         if( s_eval != NULL || s_emt != NULL ) {
12287             /* New style */
12288             char delim;
12289
12290             if( s_eval != NULL ) {
12291                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12292                     return text;
12293                 }
12294
12295                 if( delim != ']' ) {
12296                     return text;
12297                 }
12298             }
12299
12300             if( s_emt != NULL ) {
12301             }
12302         }
12303         else {
12304             /* We expect something like: [+|-]nnn.nn/dd */
12305             int score_lo = 0;
12306
12307             sep = strchr( text, '/' );
12308             if( sep == NULL || sep < (text+4) ) {
12309                 return text;
12310             }
12311
12312             time = -1; sec = -1; deci = -1;
12313             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12314                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12315                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12316                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12317                 return text;
12318             }
12319
12320             if( score_lo < 0 || score_lo >= 100 ) {
12321                 return text;
12322             }
12323
12324             if(sec >= 0) time = 600*time + 10*sec; else
12325             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12326
12327             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12328
12329             /* [HGM] PV time: now locate end of PV info */
12330             while( *++sep >= '0' && *sep <= '9'); // strip depth
12331             if(time >= 0)
12332             while( *++sep >= '0' && *sep <= '9'); // strip time
12333             if(sec >= 0)
12334             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12335             if(deci >= 0)
12336             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12337             while(*sep == ' ') sep++;
12338         }
12339
12340         if( depth <= 0 ) {
12341             return text;
12342         }
12343
12344         if( time < 0 ) {
12345             time = -1;
12346         }
12347
12348         pvInfoList[index-1].depth = depth;
12349         pvInfoList[index-1].score = score;
12350         pvInfoList[index-1].time  = 10*time; // centi-sec
12351     }
12352     return sep;
12353 }
12354
12355 void
12356 SendToProgram(message, cps)
12357      char *message;
12358      ChessProgramState *cps;
12359 {
12360     int count, outCount, error;
12361     char buf[MSG_SIZ];
12362
12363     if (cps->pr == NULL) return;
12364     Attention(cps);
12365     
12366     if (appData.debugMode) {
12367         TimeMark now;
12368         GetTimeMark(&now);
12369         fprintf(debugFP, "%ld >%-6s: %s", 
12370                 SubtractTimeMarks(&now, &programStartTime),
12371                 cps->which, message);
12372     }
12373     
12374     count = strlen(message);
12375     outCount = OutputToProcess(cps->pr, message, count, &error);
12376     if (outCount < count && !exiting 
12377                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12378         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12379         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12380             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12381                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12382                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12383             } else {
12384                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12385             }
12386             gameInfo.resultDetails = buf;
12387         }
12388         DisplayFatalError(buf, error, 1);
12389     }
12390 }
12391
12392 void
12393 ReceiveFromProgram(isr, closure, message, count, error)
12394      InputSourceRef isr;
12395      VOIDSTAR closure;
12396      char *message;
12397      int count;
12398      int error;
12399 {
12400     char *end_str;
12401     char buf[MSG_SIZ];
12402     ChessProgramState *cps = (ChessProgramState *)closure;
12403
12404     if (isr != cps->isr) return; /* Killed intentionally */
12405     if (count <= 0) {
12406         if (count == 0) {
12407             sprintf(buf,
12408                     _("Error: %s chess program (%s) exited unexpectedly"),
12409                     cps->which, cps->program);
12410         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12411                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12412                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12413                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12414                 } else {
12415                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12416                 }
12417                 gameInfo.resultDetails = buf;
12418             }
12419             RemoveInputSource(cps->isr);
12420             DisplayFatalError(buf, 0, 1);
12421         } else {
12422             sprintf(buf,
12423                     _("Error reading from %s chess program (%s)"),
12424                     cps->which, cps->program);
12425             RemoveInputSource(cps->isr);
12426
12427             /* [AS] Program is misbehaving badly... kill it */
12428             if( count == -2 ) {
12429                 DestroyChildProcess( cps->pr, 9 );
12430                 cps->pr = NoProc;
12431             }
12432
12433             DisplayFatalError(buf, error, 1);
12434         }
12435         return;
12436     }
12437     
12438     if ((end_str = strchr(message, '\r')) != NULL)
12439       *end_str = NULLCHAR;
12440     if ((end_str = strchr(message, '\n')) != NULL)
12441       *end_str = NULLCHAR;
12442     
12443     if (appData.debugMode) {
12444         TimeMark now; int print = 1;
12445         char *quote = ""; char c; int i;
12446
12447         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12448                 char start = message[0];
12449                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12450                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12451                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12452                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12453                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12454                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12455                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12456                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12457                         { quote = "# "; print = (appData.engineComments == 2); }
12458                 message[0] = start; // restore original message
12459         }
12460         if(print) {
12461                 GetTimeMark(&now);
12462                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12463                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12464                         quote,
12465                         message);
12466         }
12467     }
12468
12469     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12470     if (appData.icsEngineAnalyze) {
12471         if (strstr(message, "whisper") != NULL ||
12472              strstr(message, "kibitz") != NULL || 
12473             strstr(message, "tellics") != NULL) return;
12474     }
12475
12476     HandleMachineMove(message, cps);
12477 }
12478
12479
12480 void
12481 SendTimeControl(cps, mps, tc, inc, sd, st)
12482      ChessProgramState *cps;
12483      int mps, inc, sd, st;
12484      long tc;
12485 {
12486     char buf[MSG_SIZ];
12487     int seconds;
12488
12489     if( timeControl_2 > 0 ) {
12490         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12491             tc = timeControl_2;
12492         }
12493     }
12494     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12495     inc /= cps->timeOdds;
12496     st  /= cps->timeOdds;
12497
12498     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12499
12500     if (st > 0) {
12501       /* Set exact time per move, normally using st command */
12502       if (cps->stKludge) {
12503         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12504         seconds = st % 60;
12505         if (seconds == 0) {
12506           sprintf(buf, "level 1 %d\n", st/60);
12507         } else {
12508           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12509         }
12510       } else {
12511         sprintf(buf, "st %d\n", st);
12512       }
12513     } else {
12514       /* Set conventional or incremental time control, using level command */
12515       if (seconds == 0) {
12516         /* Note old gnuchess bug -- minutes:seconds used to not work.
12517            Fixed in later versions, but still avoid :seconds
12518            when seconds is 0. */
12519         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12520       } else {
12521         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12522                 seconds, inc/1000);
12523       }
12524     }
12525     SendToProgram(buf, cps);
12526
12527     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12528     /* Orthogonally, limit search to given depth */
12529     if (sd > 0) {
12530       if (cps->sdKludge) {
12531         sprintf(buf, "depth\n%d\n", sd);
12532       } else {
12533         sprintf(buf, "sd %d\n", sd);
12534       }
12535       SendToProgram(buf, cps);
12536     }
12537
12538     if(cps->nps > 0) { /* [HGM] nps */
12539         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12540         else {
12541                 sprintf(buf, "nps %d\n", cps->nps);
12542               SendToProgram(buf, cps);
12543         }
12544     }
12545 }
12546
12547 ChessProgramState *WhitePlayer()
12548 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12549 {
12550     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12551        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12552         return &second;
12553     return &first;
12554 }
12555
12556 void
12557 SendTimeRemaining(cps, machineWhite)
12558      ChessProgramState *cps;
12559      int /*boolean*/ machineWhite;
12560 {
12561     char message[MSG_SIZ];
12562     long time, otime;
12563
12564     /* Note: this routine must be called when the clocks are stopped
12565        or when they have *just* been set or switched; otherwise
12566        it will be off by the time since the current tick started.
12567     */
12568     if (machineWhite) {
12569         time = whiteTimeRemaining / 10;
12570         otime = blackTimeRemaining / 10;
12571     } else {
12572         time = blackTimeRemaining / 10;
12573         otime = whiteTimeRemaining / 10;
12574     }
12575     /* [HGM] translate opponent's time by time-odds factor */
12576     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12577     if (appData.debugMode) {
12578         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12579     }
12580
12581     if (time <= 0) time = 1;
12582     if (otime <= 0) otime = 1;
12583     
12584     sprintf(message, "time %ld\n", time);
12585     SendToProgram(message, cps);
12586
12587     sprintf(message, "otim %ld\n", otime);
12588     SendToProgram(message, cps);
12589 }
12590
12591 int
12592 BoolFeature(p, name, loc, cps)
12593      char **p;
12594      char *name;
12595      int *loc;
12596      ChessProgramState *cps;
12597 {
12598   char buf[MSG_SIZ];
12599   int len = strlen(name);
12600   int val;
12601   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12602     (*p) += len + 1;
12603     sscanf(*p, "%d", &val);
12604     *loc = (val != 0);
12605     while (**p && **p != ' ') (*p)++;
12606     sprintf(buf, "accepted %s\n", name);
12607     SendToProgram(buf, cps);
12608     return TRUE;
12609   }
12610   return FALSE;
12611 }
12612
12613 int
12614 IntFeature(p, name, loc, cps)
12615      char **p;
12616      char *name;
12617      int *loc;
12618      ChessProgramState *cps;
12619 {
12620   char buf[MSG_SIZ];
12621   int len = strlen(name);
12622   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12623     (*p) += len + 1;
12624     sscanf(*p, "%d", loc);
12625     while (**p && **p != ' ') (*p)++;
12626     sprintf(buf, "accepted %s\n", name);
12627     SendToProgram(buf, cps);
12628     return TRUE;
12629   }
12630   return FALSE;
12631 }
12632
12633 int
12634 StringFeature(p, name, loc, cps)
12635      char **p;
12636      char *name;
12637      char loc[];
12638      ChessProgramState *cps;
12639 {
12640   char buf[MSG_SIZ];
12641   int len = strlen(name);
12642   if (strncmp((*p), name, len) == 0
12643       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12644     (*p) += len + 2;
12645     sscanf(*p, "%[^\"]", loc);
12646     while (**p && **p != '\"') (*p)++;
12647     if (**p == '\"') (*p)++;
12648     sprintf(buf, "accepted %s\n", name);
12649     SendToProgram(buf, cps);
12650     return TRUE;
12651   }
12652   return FALSE;
12653 }
12654
12655 int 
12656 ParseOption(Option *opt, ChessProgramState *cps)
12657 // [HGM] options: process the string that defines an engine option, and determine
12658 // name, type, default value, and allowed value range
12659 {
12660         char *p, *q, buf[MSG_SIZ];
12661         int n, min = (-1)<<31, max = 1<<31, def;
12662
12663         if(p = strstr(opt->name, " -spin ")) {
12664             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12665             if(max < min) max = min; // enforce consistency
12666             if(def < min) def = min;
12667             if(def > max) def = max;
12668             opt->value = def;
12669             opt->min = min;
12670             opt->max = max;
12671             opt->type = Spin;
12672         } else if((p = strstr(opt->name, " -slider "))) {
12673             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12674             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12675             if(max < min) max = min; // enforce consistency
12676             if(def < min) def = min;
12677             if(def > max) def = max;
12678             opt->value = def;
12679             opt->min = min;
12680             opt->max = max;
12681             opt->type = Spin; // Slider;
12682         } else if((p = strstr(opt->name, " -string "))) {
12683             opt->textValue = p+9;
12684             opt->type = TextBox;
12685         } else if((p = strstr(opt->name, " -file "))) {
12686             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12687             opt->textValue = p+7;
12688             opt->type = TextBox; // FileName;
12689         } else if((p = strstr(opt->name, " -path "))) {
12690             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12691             opt->textValue = p+7;
12692             opt->type = TextBox; // PathName;
12693         } else if(p = strstr(opt->name, " -check ")) {
12694             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12695             opt->value = (def != 0);
12696             opt->type = CheckBox;
12697         } else if(p = strstr(opt->name, " -combo ")) {
12698             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12699             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12700             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12701             opt->value = n = 0;
12702             while(q = StrStr(q, " /// ")) {
12703                 n++; *q = 0;    // count choices, and null-terminate each of them
12704                 q += 5;
12705                 if(*q == '*') { // remember default, which is marked with * prefix
12706                     q++;
12707                     opt->value = n;
12708                 }
12709                 cps->comboList[cps->comboCnt++] = q;
12710             }
12711             cps->comboList[cps->comboCnt++] = NULL;
12712             opt->max = n + 1;
12713             opt->type = ComboBox;
12714         } else if(p = strstr(opt->name, " -button")) {
12715             opt->type = Button;
12716         } else if(p = strstr(opt->name, " -save")) {
12717             opt->type = SaveButton;
12718         } else return FALSE;
12719         *p = 0; // terminate option name
12720         // now look if the command-line options define a setting for this engine option.
12721         if(cps->optionSettings && cps->optionSettings[0])
12722             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12723         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12724                 sprintf(buf, "option %s", p);
12725                 if(p = strstr(buf, ",")) *p = 0;
12726                 strcat(buf, "\n");
12727                 SendToProgram(buf, cps);
12728         }
12729         return TRUE;
12730 }
12731
12732 void
12733 FeatureDone(cps, val)
12734      ChessProgramState* cps;
12735      int val;
12736 {
12737   DelayedEventCallback cb = GetDelayedEvent();
12738   if ((cb == InitBackEnd3 && cps == &first) ||
12739       (cb == TwoMachinesEventIfReady && cps == &second)) {
12740     CancelDelayedEvent();
12741     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12742   }
12743   cps->initDone = val;
12744 }
12745
12746 /* Parse feature command from engine */
12747 void
12748 ParseFeatures(args, cps)
12749      char* args;
12750      ChessProgramState *cps;  
12751 {
12752   char *p = args;
12753   char *q;
12754   int val;
12755   char buf[MSG_SIZ];
12756
12757   for (;;) {
12758     while (*p == ' ') p++;
12759     if (*p == NULLCHAR) return;
12760
12761     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12762     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12763     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12764     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12765     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12766     if (BoolFeature(&p, "reuse", &val, cps)) {
12767       /* Engine can disable reuse, but can't enable it if user said no */
12768       if (!val) cps->reuse = FALSE;
12769       continue;
12770     }
12771     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12772     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12773       if (gameMode == TwoMachinesPlay) {
12774         DisplayTwoMachinesTitle();
12775       } else {
12776         DisplayTitle("");
12777       }
12778       continue;
12779     }
12780     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12781     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12782     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12783     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12784     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12785     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12786     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12787     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12788     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12789     if (IntFeature(&p, "done", &val, cps)) {
12790       FeatureDone(cps, val);
12791       continue;
12792     }
12793     /* Added by Tord: */
12794     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12795     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12796     /* End of additions by Tord */
12797
12798     /* [HGM] added features: */
12799     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12800     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12801     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12802     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12803     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12804     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12805     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12806         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12807             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12808             SendToProgram(buf, cps);
12809             continue;
12810         }
12811         if(cps->nrOptions >= MAX_OPTIONS) {
12812             cps->nrOptions--;
12813             sprintf(buf, "%s engine has too many options\n", cps->which);
12814             DisplayError(buf, 0);
12815         }
12816         continue;
12817     }
12818     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12819     /* End of additions by HGM */
12820
12821     /* unknown feature: complain and skip */
12822     q = p;
12823     while (*q && *q != '=') q++;
12824     sprintf(buf, "rejected %.*s\n", q-p, p);
12825     SendToProgram(buf, cps);
12826     p = q;
12827     if (*p == '=') {
12828       p++;
12829       if (*p == '\"') {
12830         p++;
12831         while (*p && *p != '\"') p++;
12832         if (*p == '\"') p++;
12833       } else {
12834         while (*p && *p != ' ') p++;
12835       }
12836     }
12837   }
12838
12839 }
12840
12841 void
12842 PeriodicUpdatesEvent(newState)
12843      int newState;
12844 {
12845     if (newState == appData.periodicUpdates)
12846       return;
12847
12848     appData.periodicUpdates=newState;
12849
12850     /* Display type changes, so update it now */
12851 //    DisplayAnalysis();
12852
12853     /* Get the ball rolling again... */
12854     if (newState) {
12855         AnalysisPeriodicEvent(1);
12856         StartAnalysisClock();
12857     }
12858 }
12859
12860 void
12861 PonderNextMoveEvent(newState)
12862      int newState;
12863 {
12864     if (newState == appData.ponderNextMove) return;
12865     if (gameMode == EditPosition) EditPositionDone();
12866     if (newState) {
12867         SendToProgram("hard\n", &first);
12868         if (gameMode == TwoMachinesPlay) {
12869             SendToProgram("hard\n", &second);
12870         }
12871     } else {
12872         SendToProgram("easy\n", &first);
12873         thinkOutput[0] = NULLCHAR;
12874         if (gameMode == TwoMachinesPlay) {
12875             SendToProgram("easy\n", &second);
12876         }
12877     }
12878     appData.ponderNextMove = newState;
12879 }
12880
12881 void
12882 NewSettingEvent(option, command, value)
12883      char *command;
12884      int option, value;
12885 {
12886     char buf[MSG_SIZ];
12887
12888     if (gameMode == EditPosition) EditPositionDone();
12889     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12890     SendToProgram(buf, &first);
12891     if (gameMode == TwoMachinesPlay) {
12892         SendToProgram(buf, &second);
12893     }
12894 }
12895
12896 void
12897 ShowThinkingEvent()
12898 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12899 {
12900     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12901     int newState = appData.showThinking
12902         // [HGM] thinking: other features now need thinking output as well
12903         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12904     
12905     if (oldState == newState) return;
12906     oldState = newState;
12907     if (gameMode == EditPosition) EditPositionDone();
12908     if (oldState) {
12909         SendToProgram("post\n", &first);
12910         if (gameMode == TwoMachinesPlay) {
12911             SendToProgram("post\n", &second);
12912         }
12913     } else {
12914         SendToProgram("nopost\n", &first);
12915         thinkOutput[0] = NULLCHAR;
12916         if (gameMode == TwoMachinesPlay) {
12917             SendToProgram("nopost\n", &second);
12918         }
12919     }
12920 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12921 }
12922
12923 void
12924 AskQuestionEvent(title, question, replyPrefix, which)
12925      char *title; char *question; char *replyPrefix; char *which;
12926 {
12927   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12928   if (pr == NoProc) return;
12929   AskQuestion(title, question, replyPrefix, pr);
12930 }
12931
12932 void
12933 DisplayMove(moveNumber)
12934      int moveNumber;
12935 {
12936     char message[MSG_SIZ];
12937     char res[MSG_SIZ];
12938     char cpThinkOutput[MSG_SIZ];
12939
12940     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12941     
12942     if (moveNumber == forwardMostMove - 1 || 
12943         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12944
12945         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12946
12947         if (strchr(cpThinkOutput, '\n')) {
12948             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12949         }
12950     } else {
12951         *cpThinkOutput = NULLCHAR;
12952     }
12953
12954     /* [AS] Hide thinking from human user */
12955     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12956         *cpThinkOutput = NULLCHAR;
12957         if( thinkOutput[0] != NULLCHAR ) {
12958             int i;
12959
12960             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12961                 cpThinkOutput[i] = '.';
12962             }
12963             cpThinkOutput[i] = NULLCHAR;
12964             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12965         }
12966     }
12967
12968     if (moveNumber == forwardMostMove - 1 &&
12969         gameInfo.resultDetails != NULL) {
12970         if (gameInfo.resultDetails[0] == NULLCHAR) {
12971             sprintf(res, " %s", PGNResult(gameInfo.result));
12972         } else {
12973             sprintf(res, " {%s} %s",
12974                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12975         }
12976     } else {
12977         res[0] = NULLCHAR;
12978     }
12979
12980     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12981         DisplayMessage(res, cpThinkOutput);
12982     } else {
12983         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12984                 WhiteOnMove(moveNumber) ? " " : ".. ",
12985                 parseList[moveNumber], res);
12986         DisplayMessage(message, cpThinkOutput);
12987     }
12988 }
12989
12990 void
12991 DisplayComment(moveNumber, text)
12992      int moveNumber;
12993      char *text;
12994 {
12995     char title[MSG_SIZ];
12996     char buf[8000]; // comment can be long!
12997     int score, depth;
12998
12999     if( appData.autoDisplayComment ) {
13000         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13001             strcpy(title, "Comment");
13002         } else {
13003             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13004                     WhiteOnMove(moveNumber) ? " " : ".. ",
13005                     parseList[moveNumber]);
13006         }
13007         // [HGM] PV info: display PV info together with (or as) comment
13008         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13009             if(text == NULL) text = "";                                           
13010             score = pvInfoList[moveNumber].score;
13011             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13012                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13013             text = buf;
13014         }
13015     } else title[0] = 0;
13016
13017     if (text != NULL)
13018         CommentPopUp(title, text);
13019 }
13020
13021 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13022  * might be busy thinking or pondering.  It can be omitted if your
13023  * gnuchess is configured to stop thinking immediately on any user
13024  * input.  However, that gnuchess feature depends on the FIONREAD
13025  * ioctl, which does not work properly on some flavors of Unix.
13026  */
13027 void
13028 Attention(cps)
13029      ChessProgramState *cps;
13030 {
13031 #if ATTENTION
13032     if (!cps->useSigint) return;
13033     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13034     switch (gameMode) {
13035       case MachinePlaysWhite:
13036       case MachinePlaysBlack:
13037       case TwoMachinesPlay:
13038       case IcsPlayingWhite:
13039       case IcsPlayingBlack:
13040       case AnalyzeMode:
13041       case AnalyzeFile:
13042         /* Skip if we know it isn't thinking */
13043         if (!cps->maybeThinking) return;
13044         if (appData.debugMode)
13045           fprintf(debugFP, "Interrupting %s\n", cps->which);
13046         InterruptChildProcess(cps->pr);
13047         cps->maybeThinking = FALSE;
13048         break;
13049       default:
13050         break;
13051     }
13052 #endif /*ATTENTION*/
13053 }
13054
13055 int
13056 CheckFlags()
13057 {
13058     if (whiteTimeRemaining <= 0) {
13059         if (!whiteFlag) {
13060             whiteFlag = TRUE;
13061             if (appData.icsActive) {
13062                 if (appData.autoCallFlag &&
13063                     gameMode == IcsPlayingBlack && !blackFlag) {
13064                   SendToICS(ics_prefix);
13065                   SendToICS("flag\n");
13066                 }
13067             } else {
13068                 if (blackFlag) {
13069                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13070                 } else {
13071                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13072                     if (appData.autoCallFlag) {
13073                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13074                         return TRUE;
13075                     }
13076                 }
13077             }
13078         }
13079     }
13080     if (blackTimeRemaining <= 0) {
13081         if (!blackFlag) {
13082             blackFlag = TRUE;
13083             if (appData.icsActive) {
13084                 if (appData.autoCallFlag &&
13085                     gameMode == IcsPlayingWhite && !whiteFlag) {
13086                   SendToICS(ics_prefix);
13087                   SendToICS("flag\n");
13088                 }
13089             } else {
13090                 if (whiteFlag) {
13091                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13092                 } else {
13093                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13094                     if (appData.autoCallFlag) {
13095                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13096                         return TRUE;
13097                     }
13098                 }
13099             }
13100         }
13101     }
13102     return FALSE;
13103 }
13104
13105 void
13106 CheckTimeControl()
13107 {
13108     if (!appData.clockMode || appData.icsActive ||
13109         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13110
13111     /*
13112      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13113      */
13114     if ( !WhiteOnMove(forwardMostMove) )
13115         /* White made time control */
13116         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13117         /* [HGM] time odds: correct new time quota for time odds! */
13118                                             / WhitePlayer()->timeOdds;
13119       else
13120         /* Black made time control */
13121         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13122                                             / WhitePlayer()->other->timeOdds;
13123 }
13124
13125 void
13126 DisplayBothClocks()
13127 {
13128     int wom = gameMode == EditPosition ?
13129       !blackPlaysFirst : WhiteOnMove(currentMove);
13130     DisplayWhiteClock(whiteTimeRemaining, wom);
13131     DisplayBlackClock(blackTimeRemaining, !wom);
13132 }
13133
13134
13135 /* Timekeeping seems to be a portability nightmare.  I think everyone
13136    has ftime(), but I'm really not sure, so I'm including some ifdefs
13137    to use other calls if you don't.  Clocks will be less accurate if
13138    you have neither ftime nor gettimeofday.
13139 */
13140
13141 /* VS 2008 requires the #include outside of the function */
13142 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13143 #include <sys/timeb.h>
13144 #endif
13145
13146 /* Get the current time as a TimeMark */
13147 void
13148 GetTimeMark(tm)
13149      TimeMark *tm;
13150 {
13151 #if HAVE_GETTIMEOFDAY
13152
13153     struct timeval timeVal;
13154     struct timezone timeZone;
13155
13156     gettimeofday(&timeVal, &timeZone);
13157     tm->sec = (long) timeVal.tv_sec; 
13158     tm->ms = (int) (timeVal.tv_usec / 1000L);
13159
13160 #else /*!HAVE_GETTIMEOFDAY*/
13161 #if HAVE_FTIME
13162
13163 // include <sys/timeb.h> / moved to just above start of function
13164     struct timeb timeB;
13165
13166     ftime(&timeB);
13167     tm->sec = (long) timeB.time;
13168     tm->ms = (int) timeB.millitm;
13169
13170 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13171     tm->sec = (long) time(NULL);
13172     tm->ms = 0;
13173 #endif
13174 #endif
13175 }
13176
13177 /* Return the difference in milliseconds between two
13178    time marks.  We assume the difference will fit in a long!
13179 */
13180 long
13181 SubtractTimeMarks(tm2, tm1)
13182      TimeMark *tm2, *tm1;
13183 {
13184     return 1000L*(tm2->sec - tm1->sec) +
13185            (long) (tm2->ms - tm1->ms);
13186 }
13187
13188
13189 /*
13190  * Code to manage the game clocks.
13191  *
13192  * In tournament play, black starts the clock and then white makes a move.
13193  * We give the human user a slight advantage if he is playing white---the
13194  * clocks don't run until he makes his first move, so it takes zero time.
13195  * Also, we don't account for network lag, so we could get out of sync
13196  * with GNU Chess's clock -- but then, referees are always right.  
13197  */
13198
13199 static TimeMark tickStartTM;
13200 static long intendedTickLength;
13201
13202 long
13203 NextTickLength(timeRemaining)
13204      long timeRemaining;
13205 {
13206     long nominalTickLength, nextTickLength;
13207
13208     if (timeRemaining > 0L && timeRemaining <= 10000L)
13209       nominalTickLength = 100L;
13210     else
13211       nominalTickLength = 1000L;
13212     nextTickLength = timeRemaining % nominalTickLength;
13213     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13214
13215     return nextTickLength;
13216 }
13217
13218 /* Adjust clock one minute up or down */
13219 void
13220 AdjustClock(Boolean which, int dir)
13221 {
13222     if(which) blackTimeRemaining += 60000*dir;
13223     else      whiteTimeRemaining += 60000*dir;
13224     DisplayBothClocks();
13225 }
13226
13227 /* Stop clocks and reset to a fresh time control */
13228 void
13229 ResetClocks() 
13230 {
13231     (void) StopClockTimer();
13232     if (appData.icsActive) {
13233         whiteTimeRemaining = blackTimeRemaining = 0;
13234     } else { /* [HGM] correct new time quote for time odds */
13235         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13236         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13237     }
13238     if (whiteFlag || blackFlag) {
13239         DisplayTitle("");
13240         whiteFlag = blackFlag = FALSE;
13241     }
13242     DisplayBothClocks();
13243 }
13244
13245 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13246
13247 /* Decrement running clock by amount of time that has passed */
13248 void
13249 DecrementClocks()
13250 {
13251     long timeRemaining;
13252     long lastTickLength, fudge;
13253     TimeMark now;
13254
13255     if (!appData.clockMode) return;
13256     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13257         
13258     GetTimeMark(&now);
13259
13260     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13261
13262     /* Fudge if we woke up a little too soon */
13263     fudge = intendedTickLength - lastTickLength;
13264     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13265
13266     if (WhiteOnMove(forwardMostMove)) {
13267         if(whiteNPS >= 0) lastTickLength = 0;
13268         timeRemaining = whiteTimeRemaining -= lastTickLength;
13269         DisplayWhiteClock(whiteTimeRemaining - fudge,
13270                           WhiteOnMove(currentMove));
13271     } else {
13272         if(blackNPS >= 0) lastTickLength = 0;
13273         timeRemaining = blackTimeRemaining -= lastTickLength;
13274         DisplayBlackClock(blackTimeRemaining - fudge,
13275                           !WhiteOnMove(currentMove));
13276     }
13277
13278     if (CheckFlags()) return;
13279         
13280     tickStartTM = now;
13281     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13282     StartClockTimer(intendedTickLength);
13283
13284     /* if the time remaining has fallen below the alarm threshold, sound the
13285      * alarm. if the alarm has sounded and (due to a takeback or time control
13286      * with increment) the time remaining has increased to a level above the
13287      * threshold, reset the alarm so it can sound again. 
13288      */
13289     
13290     if (appData.icsActive && appData.icsAlarm) {
13291
13292         /* make sure we are dealing with the user's clock */
13293         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13294                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13295            )) return;
13296
13297         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13298             alarmSounded = FALSE;
13299         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13300             PlayAlarmSound();
13301             alarmSounded = TRUE;
13302         }
13303     }
13304 }
13305
13306
13307 /* A player has just moved, so stop the previously running
13308    clock and (if in clock mode) start the other one.
13309    We redisplay both clocks in case we're in ICS mode, because
13310    ICS gives us an update to both clocks after every move.
13311    Note that this routine is called *after* forwardMostMove
13312    is updated, so the last fractional tick must be subtracted
13313    from the color that is *not* on move now.
13314 */
13315 void
13316 SwitchClocks()
13317 {
13318     long lastTickLength;
13319     TimeMark now;
13320     int flagged = FALSE;
13321
13322     GetTimeMark(&now);
13323
13324     if (StopClockTimer() && appData.clockMode) {
13325         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13326         if (WhiteOnMove(forwardMostMove)) {
13327             if(blackNPS >= 0) lastTickLength = 0;
13328             blackTimeRemaining -= lastTickLength;
13329            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13330 //         if(pvInfoList[forwardMostMove-1].time == -1)
13331                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13332                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13333         } else {
13334            if(whiteNPS >= 0) lastTickLength = 0;
13335            whiteTimeRemaining -= lastTickLength;
13336            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13337 //         if(pvInfoList[forwardMostMove-1].time == -1)
13338                  pvInfoList[forwardMostMove-1].time = 
13339                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13340         }
13341         flagged = CheckFlags();
13342     }
13343     CheckTimeControl();
13344
13345     if (flagged || !appData.clockMode) return;
13346
13347     switch (gameMode) {
13348       case MachinePlaysBlack:
13349       case MachinePlaysWhite:
13350       case BeginningOfGame:
13351         if (pausing) return;
13352         break;
13353
13354       case EditGame:
13355       case PlayFromGameFile:
13356       case IcsExamining:
13357         return;
13358
13359       default:
13360         break;
13361     }
13362
13363     tickStartTM = now;
13364     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13365       whiteTimeRemaining : blackTimeRemaining);
13366     StartClockTimer(intendedTickLength);
13367 }
13368         
13369
13370 /* Stop both clocks */
13371 void
13372 StopClocks()
13373 {       
13374     long lastTickLength;
13375     TimeMark now;
13376
13377     if (!StopClockTimer()) return;
13378     if (!appData.clockMode) return;
13379
13380     GetTimeMark(&now);
13381
13382     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13383     if (WhiteOnMove(forwardMostMove)) {
13384         if(whiteNPS >= 0) lastTickLength = 0;
13385         whiteTimeRemaining -= lastTickLength;
13386         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13387     } else {
13388         if(blackNPS >= 0) lastTickLength = 0;
13389         blackTimeRemaining -= lastTickLength;
13390         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13391     }
13392     CheckFlags();
13393 }
13394         
13395 /* Start clock of player on move.  Time may have been reset, so
13396    if clock is already running, stop and restart it. */
13397 void
13398 StartClocks()
13399 {
13400     (void) StopClockTimer(); /* in case it was running already */
13401     DisplayBothClocks();
13402     if (CheckFlags()) return;
13403
13404     if (!appData.clockMode) return;
13405     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13406
13407     GetTimeMark(&tickStartTM);
13408     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13409       whiteTimeRemaining : blackTimeRemaining);
13410
13411    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13412     whiteNPS = blackNPS = -1; 
13413     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13414        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13415         whiteNPS = first.nps;
13416     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13417        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13418         blackNPS = first.nps;
13419     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13420         whiteNPS = second.nps;
13421     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13422         blackNPS = second.nps;
13423     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13424
13425     StartClockTimer(intendedTickLength);
13426 }
13427
13428 char *
13429 TimeString(ms)
13430      long ms;
13431 {
13432     long second, minute, hour, day;
13433     char *sign = "";
13434     static char buf[32];
13435     
13436     if (ms > 0 && ms <= 9900) {
13437       /* convert milliseconds to tenths, rounding up */
13438       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13439
13440       sprintf(buf, " %03.1f ", tenths/10.0);
13441       return buf;
13442     }
13443
13444     /* convert milliseconds to seconds, rounding up */
13445     /* use floating point to avoid strangeness of integer division
13446        with negative dividends on many machines */
13447     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13448
13449     if (second < 0) {
13450         sign = "-";
13451         second = -second;
13452     }
13453     
13454     day = second / (60 * 60 * 24);
13455     second = second % (60 * 60 * 24);
13456     hour = second / (60 * 60);
13457     second = second % (60 * 60);
13458     minute = second / 60;
13459     second = second % 60;
13460     
13461     if (day > 0)
13462       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13463               sign, day, hour, minute, second);
13464     else if (hour > 0)
13465       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13466     else
13467       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13468     
13469     return buf;
13470 }
13471
13472
13473 /*
13474  * This is necessary because some C libraries aren't ANSI C compliant yet.
13475  */
13476 char *
13477 StrStr(string, match)
13478      char *string, *match;
13479 {
13480     int i, length;
13481     
13482     length = strlen(match);
13483     
13484     for (i = strlen(string) - length; i >= 0; i--, string++)
13485       if (!strncmp(match, string, length))
13486         return string;
13487     
13488     return NULL;
13489 }
13490
13491 char *
13492 StrCaseStr(string, match)
13493      char *string, *match;
13494 {
13495     int i, j, length;
13496     
13497     length = strlen(match);
13498     
13499     for (i = strlen(string) - length; i >= 0; i--, string++) {
13500         for (j = 0; j < length; j++) {
13501             if (ToLower(match[j]) != ToLower(string[j]))
13502               break;
13503         }
13504         if (j == length) return string;
13505     }
13506
13507     return NULL;
13508 }
13509
13510 #ifndef _amigados
13511 int
13512 StrCaseCmp(s1, s2)
13513      char *s1, *s2;
13514 {
13515     char c1, c2;
13516     
13517     for (;;) {
13518         c1 = ToLower(*s1++);
13519         c2 = ToLower(*s2++);
13520         if (c1 > c2) return 1;
13521         if (c1 < c2) return -1;
13522         if (c1 == NULLCHAR) return 0;
13523     }
13524 }
13525
13526
13527 int
13528 ToLower(c)
13529      int c;
13530 {
13531     return isupper(c) ? tolower(c) : c;
13532 }
13533
13534
13535 int
13536 ToUpper(c)
13537      int c;
13538 {
13539     return islower(c) ? toupper(c) : c;
13540 }
13541 #endif /* !_amigados    */
13542
13543 char *
13544 StrSave(s)
13545      char *s;
13546 {
13547     char *ret;
13548
13549     if ((ret = (char *) malloc(strlen(s) + 1))) {
13550         strcpy(ret, s);
13551     }
13552     return ret;
13553 }
13554
13555 char *
13556 StrSavePtr(s, savePtr)
13557      char *s, **savePtr;
13558 {
13559     if (*savePtr) {
13560         free(*savePtr);
13561     }
13562     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13563         strcpy(*savePtr, s);
13564     }
13565     return(*savePtr);
13566 }
13567
13568 char *
13569 PGNDate()
13570 {
13571     time_t clock;
13572     struct tm *tm;
13573     char buf[MSG_SIZ];
13574
13575     clock = time((time_t *)NULL);
13576     tm = localtime(&clock);
13577     sprintf(buf, "%04d.%02d.%02d",
13578             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13579     return StrSave(buf);
13580 }
13581
13582
13583 char *
13584 PositionToFEN(move, overrideCastling)
13585      int move;
13586      char *overrideCastling;
13587 {
13588     int i, j, fromX, fromY, toX, toY;
13589     int whiteToPlay;
13590     char buf[128];
13591     char *p, *q;
13592     int emptycount;
13593     ChessSquare piece;
13594
13595     whiteToPlay = (gameMode == EditPosition) ?
13596       !blackPlaysFirst : (move % 2 == 0);
13597     p = buf;
13598
13599     /* Piece placement data */
13600     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13601         emptycount = 0;
13602         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13603             if (boards[move][i][j] == EmptySquare) {
13604                 emptycount++;
13605             } else { ChessSquare piece = boards[move][i][j];
13606                 if (emptycount > 0) {
13607                     if(emptycount<10) /* [HGM] can be >= 10 */
13608                         *p++ = '0' + emptycount;
13609                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13610                     emptycount = 0;
13611                 }
13612                 if(PieceToChar(piece) == '+') {
13613                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13614                     *p++ = '+';
13615                     piece = (ChessSquare)(DEMOTED piece);
13616                 } 
13617                 *p++ = PieceToChar(piece);
13618                 if(p[-1] == '~') {
13619                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13620                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13621                     *p++ = '~';
13622                 }
13623             }
13624         }
13625         if (emptycount > 0) {
13626             if(emptycount<10) /* [HGM] can be >= 10 */
13627                 *p++ = '0' + emptycount;
13628             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13629             emptycount = 0;
13630         }
13631         *p++ = '/';
13632     }
13633     *(p - 1) = ' ';
13634
13635     /* [HGM] print Crazyhouse or Shogi holdings */
13636     if( gameInfo.holdingsWidth ) {
13637         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13638         q = p;
13639         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13640             piece = boards[move][i][BOARD_WIDTH-1];
13641             if( piece != EmptySquare )
13642               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13643                   *p++ = PieceToChar(piece);
13644         }
13645         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13646             piece = boards[move][BOARD_HEIGHT-i-1][0];
13647             if( piece != EmptySquare )
13648               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13649                   *p++ = PieceToChar(piece);
13650         }
13651
13652         if( q == p ) *p++ = '-';
13653         *p++ = ']';
13654         *p++ = ' ';
13655     }
13656
13657     /* Active color */
13658     *p++ = whiteToPlay ? 'w' : 'b';
13659     *p++ = ' ';
13660
13661   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13662     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13663   } else {
13664   if(nrCastlingRights) {
13665      q = p;
13666      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13667        /* [HGM] write directly from rights */
13668            if(castlingRights[move][2] >= 0 &&
13669               castlingRights[move][0] >= 0   )
13670                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13671            if(castlingRights[move][2] >= 0 &&
13672               castlingRights[move][1] >= 0   )
13673                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13674            if(castlingRights[move][5] >= 0 &&
13675               castlingRights[move][3] >= 0   )
13676                 *p++ = castlingRights[move][3] + AAA;
13677            if(castlingRights[move][5] >= 0 &&
13678               castlingRights[move][4] >= 0   )
13679                 *p++ = castlingRights[move][4] + AAA;
13680      } else {
13681
13682         /* [HGM] write true castling rights */
13683         if( nrCastlingRights == 6 ) {
13684             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13685                castlingRights[move][2] >= 0  ) *p++ = 'K';
13686             if(castlingRights[move][1] == BOARD_LEFT &&
13687                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13688             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13689                castlingRights[move][5] >= 0  ) *p++ = 'k';
13690             if(castlingRights[move][4] == BOARD_LEFT &&
13691                castlingRights[move][5] >= 0  ) *p++ = 'q';
13692         }
13693      }
13694      if (q == p) *p++ = '-'; /* No castling rights */
13695      *p++ = ' ';
13696   }
13697
13698   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13699      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13700     /* En passant target square */
13701     if (move > backwardMostMove) {
13702         fromX = moveList[move - 1][0] - AAA;
13703         fromY = moveList[move - 1][1] - ONE;
13704         toX = moveList[move - 1][2] - AAA;
13705         toY = moveList[move - 1][3] - ONE;
13706         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13707             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13708             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13709             fromX == toX) {
13710             /* 2-square pawn move just happened */
13711             *p++ = toX + AAA;
13712             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13713         } else {
13714             *p++ = '-';
13715         }
13716     } else if(move == backwardMostMove) {
13717         // [HGM] perhaps we should always do it like this, and forget the above?
13718         if(epStatus[move] >= 0) {
13719             *p++ = epStatus[move] + AAA;
13720             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13721         } else {
13722             *p++ = '-';
13723         }
13724     } else {
13725         *p++ = '-';
13726     }
13727     *p++ = ' ';
13728   }
13729   }
13730
13731     /* [HGM] find reversible plies */
13732     {   int i = 0, j=move;
13733
13734         if (appData.debugMode) { int k;
13735             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13736             for(k=backwardMostMove; k<=forwardMostMove; k++)
13737                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13738
13739         }
13740
13741         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13742         if( j == backwardMostMove ) i += initialRulePlies;
13743         sprintf(p, "%d ", i);
13744         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13745     }
13746     /* Fullmove number */
13747     sprintf(p, "%d", (move / 2) + 1);
13748     
13749     return StrSave(buf);
13750 }
13751
13752 Boolean
13753 ParseFEN(board, blackPlaysFirst, fen)
13754     Board board;
13755      int *blackPlaysFirst;
13756      char *fen;
13757 {
13758     int i, j;
13759     char *p;
13760     int emptycount;
13761     ChessSquare piece;
13762
13763     p = fen;
13764
13765     /* [HGM] by default clear Crazyhouse holdings, if present */
13766     if(gameInfo.holdingsWidth) {
13767        for(i=0; i<BOARD_HEIGHT; i++) {
13768            board[i][0]             = EmptySquare; /* black holdings */
13769            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13770            board[i][1]             = (ChessSquare) 0; /* black counts */
13771            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13772        }
13773     }
13774
13775     /* Piece placement data */
13776     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13777         j = 0;
13778         for (;;) {
13779             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13780                 if (*p == '/') p++;
13781                 emptycount = gameInfo.boardWidth - j;
13782                 while (emptycount--)
13783                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13784                 break;
13785 #if(BOARD_SIZE >= 10)
13786             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13787                 p++; emptycount=10;
13788                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13789                 while (emptycount--)
13790                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13791 #endif
13792             } else if (isdigit(*p)) {
13793                 emptycount = *p++ - '0';
13794                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13795                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13796                 while (emptycount--)
13797                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13798             } else if (*p == '+' || isalpha(*p)) {
13799                 if (j >= gameInfo.boardWidth) return FALSE;
13800                 if(*p=='+') {
13801                     piece = CharToPiece(*++p);
13802                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13803                     piece = (ChessSquare) (PROMOTED piece ); p++;
13804                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13805                 } else piece = CharToPiece(*p++);
13806
13807                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13808                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13809                     piece = (ChessSquare) (PROMOTED piece);
13810                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13811                     p++;
13812                 }
13813                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13814             } else {
13815                 return FALSE;
13816             }
13817         }
13818     }
13819     while (*p == '/' || *p == ' ') p++;
13820
13821     /* [HGM] look for Crazyhouse holdings here */
13822     while(*p==' ') p++;
13823     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13824         if(*p == '[') p++;
13825         if(*p == '-' ) *p++; /* empty holdings */ else {
13826             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13827             /* if we would allow FEN reading to set board size, we would   */
13828             /* have to add holdings and shift the board read so far here   */
13829             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13830                 *p++;
13831                 if((int) piece >= (int) BlackPawn ) {
13832                     i = (int)piece - (int)BlackPawn;
13833                     i = PieceToNumber((ChessSquare)i);
13834                     if( i >= gameInfo.holdingsSize ) return FALSE;
13835                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13836                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13837                 } else {
13838                     i = (int)piece - (int)WhitePawn;
13839                     i = PieceToNumber((ChessSquare)i);
13840                     if( i >= gameInfo.holdingsSize ) return FALSE;
13841                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13842                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13843                 }
13844             }
13845         }
13846         if(*p == ']') *p++;
13847     }
13848
13849     while(*p == ' ') p++;
13850
13851     /* Active color */
13852     switch (*p++) {
13853       case 'w':
13854         *blackPlaysFirst = FALSE;
13855         break;
13856       case 'b': 
13857         *blackPlaysFirst = TRUE;
13858         break;
13859       default:
13860         return FALSE;
13861     }
13862
13863     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13864     /* return the extra info in global variiables             */
13865
13866     /* set defaults in case FEN is incomplete */
13867     FENepStatus = EP_UNKNOWN;
13868     for(i=0; i<nrCastlingRights; i++ ) {
13869         FENcastlingRights[i] =
13870             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13871     }   /* assume possible unless obviously impossible */
13872     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13873     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13874     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13875     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13876     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13877     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13878     FENrulePlies = 0;
13879
13880     while(*p==' ') p++;
13881     if(nrCastlingRights) {
13882       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13883           /* castling indicator present, so default becomes no castlings */
13884           for(i=0; i<nrCastlingRights; i++ ) {
13885                  FENcastlingRights[i] = -1;
13886           }
13887       }
13888       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13889              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13890              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13891              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13892         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13893
13894         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13895             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13896             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13897         }
13898         switch(c) {
13899           case'K':
13900               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13901               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13902               FENcastlingRights[2] = whiteKingFile;
13903               break;
13904           case'Q':
13905               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13906               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13907               FENcastlingRights[2] = whiteKingFile;
13908               break;
13909           case'k':
13910               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13911               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13912               FENcastlingRights[5] = blackKingFile;
13913               break;
13914           case'q':
13915               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13916               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13917               FENcastlingRights[5] = blackKingFile;
13918           case '-':
13919               break;
13920           default: /* FRC castlings */
13921               if(c >= 'a') { /* black rights */
13922                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13923                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13924                   if(i == BOARD_RGHT) break;
13925                   FENcastlingRights[5] = i;
13926                   c -= AAA;
13927                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13928                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13929                   if(c > i)
13930                       FENcastlingRights[3] = c;
13931                   else
13932                       FENcastlingRights[4] = c;
13933               } else { /* white rights */
13934                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13935                     if(board[0][i] == WhiteKing) break;
13936                   if(i == BOARD_RGHT) break;
13937                   FENcastlingRights[2] = i;
13938                   c -= AAA - 'a' + 'A';
13939                   if(board[0][c] >= WhiteKing) break;
13940                   if(c > i)
13941                       FENcastlingRights[0] = c;
13942                   else
13943                       FENcastlingRights[1] = c;
13944               }
13945         }
13946       }
13947     if (appData.debugMode) {
13948         fprintf(debugFP, "FEN castling rights:");
13949         for(i=0; i<nrCastlingRights; i++)
13950         fprintf(debugFP, " %d", FENcastlingRights[i]);
13951         fprintf(debugFP, "\n");
13952     }
13953
13954       while(*p==' ') p++;
13955     }
13956
13957     /* read e.p. field in games that know e.p. capture */
13958     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13959        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13960       if(*p=='-') {
13961         p++; FENepStatus = EP_NONE;
13962       } else {
13963          char c = *p++ - AAA;
13964
13965          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13966          if(*p >= '0' && *p <='9') *p++;
13967          FENepStatus = c;
13968       }
13969     }
13970
13971
13972     if(sscanf(p, "%d", &i) == 1) {
13973         FENrulePlies = i; /* 50-move ply counter */
13974         /* (The move number is still ignored)    */
13975     }
13976
13977     return TRUE;
13978 }
13979       
13980 void
13981 EditPositionPasteFEN(char *fen)
13982 {
13983   if (fen != NULL) {
13984     Board initial_position;
13985
13986     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13987       DisplayError(_("Bad FEN position in clipboard"), 0);
13988       return ;
13989     } else {
13990       int savedBlackPlaysFirst = blackPlaysFirst;
13991       EditPositionEvent();
13992       blackPlaysFirst = savedBlackPlaysFirst;
13993       CopyBoard(boards[0], initial_position);
13994           /* [HGM] copy FEN attributes as well */
13995           {   int i;
13996               initialRulePlies = FENrulePlies;
13997               epStatus[0] = FENepStatus;
13998               for( i=0; i<nrCastlingRights; i++ )
13999                   castlingRights[0][i] = FENcastlingRights[i];
14000           }
14001       EditPositionDone();
14002       DisplayBothClocks();
14003       DrawPosition(FALSE, boards[currentMove]);
14004     }
14005   }
14006 }
14007
14008 static char cseq[12] = "\\   ";
14009
14010 Boolean set_cont_sequence(char *new_seq)
14011 {
14012     int len;
14013     Boolean ret;
14014
14015     // handle bad attempts to set the sequence
14016         if (!new_seq)
14017                 return 0; // acceptable error - no debug
14018
14019     len = strlen(new_seq);
14020     ret = (len > 0) && (len < sizeof(cseq));
14021     if (ret)
14022         strcpy(cseq, new_seq);
14023     else if (appData.debugMode)
14024         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14025     return ret;
14026 }
14027
14028 /*
14029     reformat a source message so words don't cross the width boundary.  internal
14030     newlines are not removed.  returns the wrapped size (no null character unless
14031     included in source message).  If dest is NULL, only calculate the size required
14032     for the dest buffer.  lp argument indicats line position upon entry, and it's
14033     passed back upon exit.
14034 */
14035 int wrap(char *dest, char *src, int count, int width, int *lp)
14036 {
14037     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14038
14039     cseq_len = strlen(cseq);
14040     old_line = line = *lp;
14041     ansi = len = clen = 0;
14042
14043     for (i=0; i < count; i++)
14044     {
14045         if (src[i] == '\033')
14046             ansi = 1;
14047
14048         // if we hit the width, back up
14049         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14050         {
14051             // store i & len in case the word is too long
14052             old_i = i, old_len = len;
14053
14054             // find the end of the last word
14055             while (i && src[i] != ' ' && src[i] != '\n')
14056             {
14057                 i--;
14058                 len--;
14059             }
14060
14061             // word too long?  restore i & len before splitting it
14062             if ((old_i-i+clen) >= width)
14063             {
14064                 i = old_i;
14065                 len = old_len;
14066             }
14067
14068             // extra space?
14069             if (i && src[i-1] == ' ')
14070                 len--;
14071
14072             if (src[i] != ' ' && src[i] != '\n')
14073             {
14074                 i--;
14075                 if (len)
14076                     len--;
14077             }
14078
14079             // now append the newline and continuation sequence
14080             if (dest)
14081                 dest[len] = '\n';
14082             len++;
14083             if (dest)
14084                 strncpy(dest+len, cseq, cseq_len);
14085             len += cseq_len;
14086             line = cseq_len;
14087             clen = cseq_len;
14088             continue;
14089         }
14090
14091         if (dest)
14092             dest[len] = src[i];
14093         len++;
14094         if (!ansi)
14095             line++;
14096         if (src[i] == '\n')
14097             line = 0;
14098         if (src[i] == 'm')
14099             ansi = 0;
14100     }
14101     if (dest && appData.debugMode)
14102     {
14103         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14104             count, width, line, len, *lp);
14105         show_bytes(debugFP, src, count);
14106         fprintf(debugFP, "\ndest: ");
14107         show_bytes(debugFP, dest, len);
14108         fprintf(debugFP, "\n");
14109     }
14110     *lp = dest ? line : old_line;
14111
14112     return len;
14113 }