fixed bug when switching to variantsuper
[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        break;
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(TRUE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(TRUE); }
2016 }
2017
2018 static int loggedOn = FALSE;
2019
2020 /*-- Game start info cache: --*/
2021 int gs_gamenum;
2022 char gs_kind[MSG_SIZ];
2023 static char player1Name[128] = "";
2024 static char player2Name[128] = "";
2025 static char cont_seq[] = "\n\\   ";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2029
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2032
2033 void
2034 read_from_ics(isr, closure, data, count, error)
2035      InputSourceRef isr;
2036      VOIDSTAR closure;
2037      char *data;
2038      int count;
2039      int error;
2040 {
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2050     
2051     static int started = STARTED_NONE;
2052     static char parse[20000];
2053     static int parse_pos = 0;
2054     static char buf[BUF_SIZE + 1];
2055     static int firstTime = TRUE, intfSet = FALSE;
2056     static ColorClass prevColor = ColorNormal;
2057     static int savingComment = FALSE;
2058     static int cmatch = 0; // continuation sequence match
2059     char *bp;
2060     char str[500];
2061     int i, oldi;
2062     int buf_len;
2063     int next_out;
2064     int tkind;
2065     int backup;    /* [DM] For zippy color lines */
2066     char *p;
2067     char talker[MSG_SIZ]; // [HGM] chat
2068     int channel;
2069
2070     if (appData.debugMode) {
2071       if (!error) {
2072         fprintf(debugFP, "<ICS: ");
2073         show_bytes(debugFP, data, count);
2074         fprintf(debugFP, "\n");
2075       }
2076     }
2077
2078     if (appData.debugMode) { int f = forwardMostMove;
2079         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2080                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2081     }
2082     if (count > 0) {
2083         /* If last read ended with a partial line that we couldn't parse,
2084            prepend it to the new read and try again. */
2085         if (leftover_len > 0) {
2086             for (i=0; i<leftover_len; i++)
2087               buf[i] = buf[leftover_start + i];
2088         }
2089
2090     /* copy new characters into the buffer */
2091     bp = buf + leftover_len;
2092     buf_len=leftover_len;
2093     for (i=0; i<count; i++)
2094     {
2095         // ignore these
2096         if (data[i] == '\r')
2097             continue;
2098
2099         // join lines split by ICS?
2100         if (!appData.noJoin)
2101         {
2102             /*
2103                 Joining just consists of finding matches against the
2104                 continuation sequence, and discarding that sequence
2105                 if found instead of copying it.  So, until a match
2106                 fails, there's nothing to do since it might be the
2107                 complete sequence, and thus, something we don't want
2108                 copied.
2109             */
2110             if (data[i] == cont_seq[cmatch])
2111             {
2112                 cmatch++;
2113                 if (cmatch == strlen(cont_seq))
2114                 {
2115                     cmatch = 0; // complete match.  just reset the counter
2116
2117                     /*
2118                         it's possible for the ICS to not include the space
2119                         at the end of the last word, making our [correct]
2120                         join operation fuse two separate words.  the server
2121                         does this when the space occurs at the width setting.
2122                     */
2123                     if (!buf_len || buf[buf_len-1] != ' ')
2124                     {
2125                         *bp++ = ' ';
2126                         buf_len++;
2127                     }
2128                 }
2129                 continue;
2130             }
2131             else if (cmatch)
2132             {
2133                 /*
2134                     match failed, so we have to copy what matched before
2135                     falling through and copying this character.  In reality,
2136                     this will only ever be just the newline character, but
2137                     it doesn't hurt to be precise.
2138                 */
2139                 strncpy(bp, cont_seq, cmatch);
2140                 bp += cmatch;
2141                 buf_len += cmatch;
2142                 cmatch = 0;
2143             }
2144         }
2145
2146         // copy this char
2147         *bp++ = data[i];
2148         buf_len++;
2149     }
2150
2151         buf[buf_len] = NULLCHAR;
2152         next_out = leftover_len;
2153         leftover_start = 0;
2154         
2155         i = 0;
2156         while (i < buf_len) {
2157             /* Deal with part of the TELNET option negotiation
2158                protocol.  We refuse to do anything beyond the
2159                defaults, except that we allow the WILL ECHO option,
2160                which ICS uses to turn off password echoing when we are
2161                directly connected to it.  We reject this option
2162                if localLineEditing mode is on (always on in xboard)
2163                and we are talking to port 23, which might be a real
2164                telnet server that will try to keep WILL ECHO on permanently.
2165              */
2166             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2167                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2168                 unsigned char option;
2169                 oldi = i;
2170                 switch ((unsigned char) buf[++i]) {
2171                   case TN_WILL:
2172                     if (appData.debugMode)
2173                       fprintf(debugFP, "\n<WILL ");
2174                     switch (option = (unsigned char) buf[++i]) {
2175                       case TN_ECHO:
2176                         if (appData.debugMode)
2177                           fprintf(debugFP, "ECHO ");
2178                         /* Reply only if this is a change, according
2179                            to the protocol rules. */
2180                         if (remoteEchoOption) break;
2181                         if (appData.localLineEditing &&
2182                             atoi(appData.icsPort) == TN_PORT) {
2183                             TelnetRequest(TN_DONT, TN_ECHO);
2184                         } else {
2185                             EchoOff();
2186                             TelnetRequest(TN_DO, TN_ECHO);
2187                             remoteEchoOption = TRUE;
2188                         }
2189                         break;
2190                       default:
2191                         if (appData.debugMode)
2192                           fprintf(debugFP, "%d ", option);
2193                         /* Whatever this is, we don't want it. */
2194                         TelnetRequest(TN_DONT, option);
2195                         break;
2196                     }
2197                     break;
2198                   case TN_WONT:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<WONT ");
2201                     switch (option = (unsigned char) buf[++i]) {
2202                       case TN_ECHO:
2203                         if (appData.debugMode)
2204                           fprintf(debugFP, "ECHO ");
2205                         /* Reply only if this is a change, according
2206                            to the protocol rules. */
2207                         if (!remoteEchoOption) break;
2208                         EchoOn();
2209                         TelnetRequest(TN_DONT, TN_ECHO);
2210                         remoteEchoOption = FALSE;
2211                         break;
2212                       default:
2213                         if (appData.debugMode)
2214                           fprintf(debugFP, "%d ", (unsigned char) option);
2215                         /* Whatever this is, it must already be turned
2216                            off, because we never agree to turn on
2217                            anything non-default, so according to the
2218                            protocol rules, we don't reply. */
2219                         break;
2220                     }
2221                     break;
2222                   case TN_DO:
2223                     if (appData.debugMode)
2224                       fprintf(debugFP, "\n<DO ");
2225                     switch (option = (unsigned char) buf[++i]) {
2226                       default:
2227                         /* Whatever this is, we refuse to do it. */
2228                         if (appData.debugMode)
2229                           fprintf(debugFP, "%d ", option);
2230                         TelnetRequest(TN_WONT, option);
2231                         break;
2232                     }
2233                     break;
2234                   case TN_DONT:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<DONT ");
2237                     switch (option = (unsigned char) buf[++i]) {
2238                       default:
2239                         if (appData.debugMode)
2240                           fprintf(debugFP, "%d ", option);
2241                         /* Whatever this is, we are already not doing
2242                            it, because we never agree to do anything
2243                            non-default, so according to the protocol
2244                            rules, we don't reply. */
2245                         break;
2246                     }
2247                     break;
2248                   case TN_IAC:
2249                     if (appData.debugMode)
2250                       fprintf(debugFP, "\n<IAC ");
2251                     /* Doubled IAC; pass it through */
2252                     i--;
2253                     break;
2254                   default:
2255                     if (appData.debugMode)
2256                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2257                     /* Drop all other telnet commands on the floor */
2258                     break;
2259                 }
2260                 if (oldi > next_out)
2261                   SendToPlayer(&buf[next_out], oldi - next_out);
2262                 if (++i > next_out)
2263                   next_out = i;
2264                 continue;
2265             }
2266                 
2267             /* OK, this at least will *usually* work */
2268             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2269                 loggedOn = TRUE;
2270             }
2271             
2272             if (loggedOn && !intfSet) {
2273                 if (ics_type == ICS_ICC) {
2274                   sprintf(str,
2275                           "/set-quietly interface %s\n/set-quietly style 12\n",
2276                           programVersion);
2277                 } else if (ics_type == ICS_CHESSNET) {
2278                   sprintf(str, "/style 12\n");
2279                 } else {
2280                   strcpy(str, "alias $ @\n$set interface ");
2281                   strcat(str, programVersion);
2282                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2283 #ifdef WIN32
2284                   strcat(str, "$iset nohighlight 1\n");
2285 #endif
2286                   strcat(str, "$iset lock 1\n$style 12\n");
2287                 }
2288                 SendToICS(str);
2289                 NotifyFrontendLogin();
2290                 intfSet = TRUE;
2291             }
2292
2293             if (started == STARTED_COMMENT) {
2294                 /* Accumulate characters in comment */
2295                 parse[parse_pos++] = buf[i];
2296                 if (buf[i] == '\n') {
2297                     parse[parse_pos] = NULLCHAR;
2298                     if(chattingPartner>=0) {
2299                         char mess[MSG_SIZ];
2300                         sprintf(mess, "%s%s", talker, parse);
2301                         OutputChatMessage(chattingPartner, mess);
2302                         chattingPartner = -1;
2303                     } else
2304                     if(!suppressKibitz) // [HGM] kibitz
2305                         AppendComment(forwardMostMove, StripHighlight(parse));
2306                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2307                         int nrDigit = 0, nrAlph = 0, i;
2308                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2309                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2310                         parse[parse_pos] = NULLCHAR;
2311                         // try to be smart: if it does not look like search info, it should go to
2312                         // ICS interaction window after all, not to engine-output window.
2313                         for(i=0; i<parse_pos; i++) { // count letters and digits
2314                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2315                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2316                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2317                         }
2318                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2319                             int depth=0; float score;
2320                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2321                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2322                                 pvInfoList[forwardMostMove-1].depth = depth;
2323                                 pvInfoList[forwardMostMove-1].score = 100*score;
2324                             }
2325                             OutputKibitz(suppressKibitz, parse);
2326                         } else {
2327                             char tmp[MSG_SIZ];
2328                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2329                             SendToPlayer(tmp, strlen(tmp));
2330                         }
2331                     }
2332                     started = STARTED_NONE;
2333                 } else {
2334                     /* Don't match patterns against characters in chatter */
2335                     i++;
2336                     continue;
2337                 }
2338             }
2339             if (started == STARTED_CHATTER) {
2340                 if (buf[i] != '\n') {
2341                     /* Don't match patterns against characters in chatter */
2342                     i++;
2343                     continue;
2344                 }
2345                 started = STARTED_NONE;
2346             }
2347
2348             /* Kludge to deal with rcmd protocol */
2349             if (firstTime && looking_at(buf, &i, "\001*")) {
2350                 DisplayFatalError(&buf[1], 0, 1);
2351                 continue;
2352             } else {
2353                 firstTime = FALSE;
2354             }
2355
2356             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2357                 ics_type = ICS_ICC;
2358                 ics_prefix = "/";
2359                 if (appData.debugMode)
2360                   fprintf(debugFP, "ics_type %d\n", ics_type);
2361                 continue;
2362             }
2363             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2364                 ics_type = ICS_FICS;
2365                 ics_prefix = "$";
2366                 if (appData.debugMode)
2367                   fprintf(debugFP, "ics_type %d\n", ics_type);
2368                 continue;
2369             }
2370             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2371                 ics_type = ICS_CHESSNET;
2372                 ics_prefix = "/";
2373                 if (appData.debugMode)
2374                   fprintf(debugFP, "ics_type %d\n", ics_type);
2375                 continue;
2376             }
2377
2378             if (!loggedOn &&
2379                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2380                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2381                  looking_at(buf, &i, "will be \"*\""))) {
2382               strcpy(ics_handle, star_match[0]);
2383               continue;
2384             }
2385
2386             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2387               char buf[MSG_SIZ];
2388               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2389               DisplayIcsInteractionTitle(buf);
2390               have_set_title = TRUE;
2391             }
2392
2393             /* skip finger notes */
2394             if (started == STARTED_NONE &&
2395                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2396                  (buf[i] == '1' && buf[i+1] == '0')) &&
2397                 buf[i+2] == ':' && buf[i+3] == ' ') {
2398               started = STARTED_CHATTER;
2399               i += 3;
2400               continue;
2401             }
2402
2403             /* skip formula vars */
2404             if (started == STARTED_NONE &&
2405                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2406               started = STARTED_CHATTER;
2407               i += 3;
2408               continue;
2409             }
2410
2411             oldi = i;
2412             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2413             if (appData.autoKibitz && started == STARTED_NONE && 
2414                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2415                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2416                 if(looking_at(buf, &i, "* kibitzes: ") &&
2417                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2418                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2419                         suppressKibitz = TRUE;
2420                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2421                                 && (gameMode == IcsPlayingWhite)) ||
2422                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2423                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2424                             started = STARTED_CHATTER; // own kibitz we simply discard
2425                         else {
2426                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2427                             parse_pos = 0; parse[0] = NULLCHAR;
2428                             savingComment = TRUE;
2429                             suppressKibitz = gameMode != IcsObserving ? 2 :
2430                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2431                         } 
2432                         continue;
2433                 } else
2434                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2435                     started = STARTED_CHATTER;
2436                     suppressKibitz = TRUE;
2437                 }
2438             } // [HGM] kibitz: end of patch
2439
2440 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2441
2442             // [HGM] chat: intercept tells by users for which we have an open chat window
2443             channel = -1;
2444             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2445                                            looking_at(buf, &i, "* whispers:") ||
2446                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2447                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2448                 int p;
2449                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2450                 chattingPartner = -1;
2451
2452                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2453                 for(p=0; p<MAX_CHAT; p++) {
2454                     if(channel == atoi(chatPartner[p])) {
2455                     talker[0] = '['; strcat(talker, "]");
2456                     chattingPartner = p; break;
2457                     }
2458                 } else
2459                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2460                 for(p=0; p<MAX_CHAT; p++) {
2461                     if(!strcmp("WHISPER", chatPartner[p])) {
2462                         talker[0] = '['; strcat(talker, "]");
2463                         chattingPartner = p; break;
2464                     }
2465                 }
2466                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2467                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2468                     talker[0] = 0;
2469                     chattingPartner = p; break;
2470                 }
2471                 if(chattingPartner<0) i = oldi; else {
2472                     started = STARTED_COMMENT;
2473                     parse_pos = 0; parse[0] = NULLCHAR;
2474                     savingComment = TRUE;
2475                     suppressKibitz = TRUE;
2476                 }
2477             } // [HGM] chat: end of patch
2478
2479             if (appData.zippyTalk || appData.zippyPlay) {
2480                 /* [DM] Backup address for color zippy lines */
2481                 backup = i;
2482 #if ZIPPY
2483        #ifdef WIN32
2484                if (loggedOn == TRUE)
2485                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2486                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2487        #else
2488                 if (ZippyControl(buf, &i) ||
2489                     ZippyConverse(buf, &i) ||
2490                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2491                       loggedOn = TRUE;
2492                       if (!appData.colorize) continue;
2493                 }
2494        #endif
2495 #endif
2496             } // [DM] 'else { ' deleted
2497                 if (
2498                     /* Regular tells and says */
2499                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2500                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2501                     looking_at(buf, &i, "* says: ") ||
2502                     /* Don't color "message" or "messages" output */
2503                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2504                     looking_at(buf, &i, "*. * at *:*: ") ||
2505                     looking_at(buf, &i, "--* (*:*): ") ||
2506                     /* Message notifications (same color as tells) */
2507                     looking_at(buf, &i, "* has left a message ") ||
2508                     looking_at(buf, &i, "* just sent you a message:\n") ||
2509                     /* Whispers and kibitzes */
2510                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2511                     looking_at(buf, &i, "* kibitzes: ") ||
2512                     /* Channel tells */
2513                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2514
2515                   if (tkind == 1 && strchr(star_match[0], ':')) {
2516                       /* Avoid "tells you:" spoofs in channels */
2517                      tkind = 3;
2518                   }
2519                   if (star_match[0][0] == NULLCHAR ||
2520                       strchr(star_match[0], ' ') ||
2521                       (tkind == 3 && strchr(star_match[1], ' '))) {
2522                     /* Reject bogus matches */
2523                     i = oldi;
2524                   } else {
2525                     if (appData.colorize) {
2526                       if (oldi > next_out) {
2527                         SendToPlayer(&buf[next_out], oldi - next_out);
2528                         next_out = oldi;
2529                       }
2530                       switch (tkind) {
2531                       case 1:
2532                         Colorize(ColorTell, FALSE);
2533                         curColor = ColorTell;
2534                         break;
2535                       case 2:
2536                         Colorize(ColorKibitz, FALSE);
2537                         curColor = ColorKibitz;
2538                         break;
2539                       case 3:
2540                         p = strrchr(star_match[1], '(');
2541                         if (p == NULL) {
2542                           p = star_match[1];
2543                         } else {
2544                           p++;
2545                         }
2546                         if (atoi(p) == 1) {
2547                           Colorize(ColorChannel1, FALSE);
2548                           curColor = ColorChannel1;
2549                         } else {
2550                           Colorize(ColorChannel, FALSE);
2551                           curColor = ColorChannel;
2552                         }
2553                         break;
2554                       case 5:
2555                         curColor = ColorNormal;
2556                         break;
2557                       }
2558                     }
2559                     if (started == STARTED_NONE && appData.autoComment &&
2560                         (gameMode == IcsObserving ||
2561                          gameMode == IcsPlayingWhite ||
2562                          gameMode == IcsPlayingBlack)) {
2563                       parse_pos = i - oldi;
2564                       memcpy(parse, &buf[oldi], parse_pos);
2565                       parse[parse_pos] = NULLCHAR;
2566                       started = STARTED_COMMENT;
2567                       savingComment = TRUE;
2568                     } else {
2569                       started = STARTED_CHATTER;
2570                       savingComment = FALSE;
2571                     }
2572                     loggedOn = TRUE;
2573                     continue;
2574                   }
2575                 }
2576
2577                 if (looking_at(buf, &i, "* s-shouts: ") ||
2578                     looking_at(buf, &i, "* c-shouts: ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorSShout, FALSE);
2585                         curColor = ColorSShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at(buf, &i, "--->")) {
2593                     loggedOn = TRUE;
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "* shouts: ") ||
2598                     looking_at(buf, &i, "--> ")) {
2599                     if (appData.colorize) {
2600                         if (oldi > next_out) {
2601                             SendToPlayer(&buf[next_out], oldi - next_out);
2602                             next_out = oldi;
2603                         }
2604                         Colorize(ColorShout, FALSE);
2605                         curColor = ColorShout;
2606                     }
2607                     loggedOn = TRUE;
2608                     started = STARTED_CHATTER;
2609                     continue;
2610                 }
2611
2612                 if (looking_at( buf, &i, "Challenge:")) {
2613                     if (appData.colorize) {
2614                         if (oldi > next_out) {
2615                             SendToPlayer(&buf[next_out], oldi - next_out);
2616                             next_out = oldi;
2617                         }
2618                         Colorize(ColorChallenge, FALSE);
2619                         curColor = ColorChallenge;
2620                     }
2621                     loggedOn = TRUE;
2622                     continue;
2623                 }
2624
2625                 if (looking_at(buf, &i, "* offers you") ||
2626                     looking_at(buf, &i, "* offers to be") ||
2627                     looking_at(buf, &i, "* would like to") ||
2628                     looking_at(buf, &i, "* requests to") ||
2629                     looking_at(buf, &i, "Your opponent offers") ||
2630                     looking_at(buf, &i, "Your opponent requests")) {
2631
2632                     if (appData.colorize) {
2633                         if (oldi > next_out) {
2634                             SendToPlayer(&buf[next_out], oldi - next_out);
2635                             next_out = oldi;
2636                         }
2637                         Colorize(ColorRequest, FALSE);
2638                         curColor = ColorRequest;
2639                     }
2640                     continue;
2641                 }
2642
2643                 if (looking_at(buf, &i, "* (*) seeking")) {
2644                     if (appData.colorize) {
2645                         if (oldi > next_out) {
2646                             SendToPlayer(&buf[next_out], oldi - next_out);
2647                             next_out = oldi;
2648                         }
2649                         Colorize(ColorSeek, FALSE);
2650                         curColor = ColorSeek;
2651                     }
2652                     continue;
2653             }
2654
2655             if (looking_at(buf, &i, "\\   ")) {
2656                 if (prevColor != ColorNormal) {
2657                     if (oldi > next_out) {
2658                         SendToPlayer(&buf[next_out], oldi - next_out);
2659                         next_out = oldi;
2660                     }
2661                     Colorize(prevColor, TRUE);
2662                     curColor = prevColor;
2663                 }
2664                 if (savingComment) {
2665                     parse_pos = i - oldi;
2666                     memcpy(parse, &buf[oldi], parse_pos);
2667                     parse[parse_pos] = NULLCHAR;
2668                     started = STARTED_COMMENT;
2669                 } else {
2670                     started = STARTED_CHATTER;
2671                 }
2672                 continue;
2673             }
2674
2675             if (looking_at(buf, &i, "Black Strength :") ||
2676                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2677                 looking_at(buf, &i, "<10>") ||
2678                 looking_at(buf, &i, "#@#")) {
2679                 /* Wrong board style */
2680                 loggedOn = TRUE;
2681                 SendToICS(ics_prefix);
2682                 SendToICS("set style 12\n");
2683                 SendToICS(ics_prefix);
2684                 SendToICS("refresh\n");
2685                 continue;
2686             }
2687             
2688             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2689                 ICSInitScript();
2690                 have_sent_ICS_logon = 1;
2691                 continue;
2692             }
2693               
2694             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2695                 (looking_at(buf, &i, "\n<12> ") ||
2696                  looking_at(buf, &i, "<12> "))) {
2697                 loggedOn = TRUE;
2698                 if (oldi > next_out) {
2699                     SendToPlayer(&buf[next_out], oldi - next_out);
2700                 }
2701                 next_out = i;
2702                 started = STARTED_BOARD;
2703                 parse_pos = 0;
2704                 continue;
2705             }
2706
2707             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2708                 looking_at(buf, &i, "<b1> ")) {
2709                 if (oldi > next_out) {
2710                     SendToPlayer(&buf[next_out], oldi - next_out);
2711                 }
2712                 next_out = i;
2713                 started = STARTED_HOLDINGS;
2714                 parse_pos = 0;
2715                 continue;
2716             }
2717
2718             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2719                 loggedOn = TRUE;
2720                 /* Header for a move list -- first line */
2721
2722                 switch (ics_getting_history) {
2723                   case H_FALSE:
2724                     switch (gameMode) {
2725                       case IcsIdle:
2726                       case BeginningOfGame:
2727                         /* User typed "moves" or "oldmoves" while we
2728                            were idle.  Pretend we asked for these
2729                            moves and soak them up so user can step
2730                            through them and/or save them.
2731                            */
2732                         Reset(TRUE, TRUE);
2733                         gameMode = IcsObserving;
2734                         ModeHighlight();
2735                         ics_gamenum = -1;
2736                         ics_getting_history = H_GOT_UNREQ_HEADER;
2737                         break;
2738                       case EditGame: /*?*/
2739                       case EditPosition: /*?*/
2740                         /* Should above feature work in these modes too? */
2741                         /* For now it doesn't */
2742                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2743                         break;
2744                       default:
2745                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2746                         break;
2747                     }
2748                     break;
2749                   case H_REQUESTED:
2750                     /* Is this the right one? */
2751                     if (gameInfo.white && gameInfo.black &&
2752                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2753                         strcmp(gameInfo.black, star_match[2]) == 0) {
2754                         /* All is well */
2755                         ics_getting_history = H_GOT_REQ_HEADER;
2756                     }
2757                     break;
2758                   case H_GOT_REQ_HEADER:
2759                   case H_GOT_UNREQ_HEADER:
2760                   case H_GOT_UNWANTED_HEADER:
2761                   case H_GETTING_MOVES:
2762                     /* Should not happen */
2763                     DisplayError(_("Error gathering move list: two headers"), 0);
2764                     ics_getting_history = H_FALSE;
2765                     break;
2766                 }
2767
2768                 /* Save player ratings into gameInfo if needed */
2769                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2770                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2771                     (gameInfo.whiteRating == -1 ||
2772                      gameInfo.blackRating == -1)) {
2773
2774                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2775                     gameInfo.blackRating = string_to_rating(star_match[3]);
2776                     if (appData.debugMode)
2777                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2778                               gameInfo.whiteRating, gameInfo.blackRating);
2779                 }
2780                 continue;
2781             }
2782
2783             if (looking_at(buf, &i,
2784               "* * match, initial time: * minute*, increment: * second")) {
2785                 /* Header for a move list -- second line */
2786                 /* Initial board will follow if this is a wild game */
2787                 if (gameInfo.event != NULL) free(gameInfo.event);
2788                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2789                 gameInfo.event = StrSave(str);
2790                 /* [HGM] we switched variant. Translate boards if needed. */
2791                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2792                 continue;
2793             }
2794
2795             if (looking_at(buf, &i, "Move  ")) {
2796                 /* Beginning of a move list */
2797                 switch (ics_getting_history) {
2798                   case H_FALSE:
2799                     /* Normally should not happen */
2800                     /* Maybe user hit reset while we were parsing */
2801                     break;
2802                   case H_REQUESTED:
2803                     /* Happens if we are ignoring a move list that is not
2804                      * the one we just requested.  Common if the user
2805                      * tries to observe two games without turning off
2806                      * getMoveList */
2807                     break;
2808                   case H_GETTING_MOVES:
2809                     /* Should not happen */
2810                     DisplayError(_("Error gathering move list: nested"), 0);
2811                     ics_getting_history = H_FALSE;
2812                     break;
2813                   case H_GOT_REQ_HEADER:
2814                     ics_getting_history = H_GETTING_MOVES;
2815                     started = STARTED_MOVES;
2816                     parse_pos = 0;
2817                     if (oldi > next_out) {
2818                         SendToPlayer(&buf[next_out], oldi - next_out);
2819                     }
2820                     break;
2821                   case H_GOT_UNREQ_HEADER:
2822                     ics_getting_history = H_GETTING_MOVES;
2823                     started = STARTED_MOVES_NOHIDE;
2824                     parse_pos = 0;
2825                     break;
2826                   case H_GOT_UNWANTED_HEADER:
2827                     ics_getting_history = H_FALSE;
2828                     break;
2829                 }
2830                 continue;
2831             }                           
2832             
2833             if (looking_at(buf, &i, "% ") ||
2834                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2835                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2836                 savingComment = FALSE;
2837                 switch (started) {
2838                   case STARTED_MOVES:
2839                   case STARTED_MOVES_NOHIDE:
2840                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2841                     parse[parse_pos + i - oldi] = NULLCHAR;
2842                     ParseGameHistory(parse);
2843 #if ZIPPY
2844                     if (appData.zippyPlay && first.initDone) {
2845                         FeedMovesToProgram(&first, forwardMostMove);
2846                         if (gameMode == IcsPlayingWhite) {
2847                             if (WhiteOnMove(forwardMostMove)) {
2848                                 if (first.sendTime) {
2849                                   if (first.useColors) {
2850                                     SendToProgram("black\n", &first); 
2851                                   }
2852                                   SendTimeRemaining(&first, TRUE);
2853                                 }
2854                                 if (first.useColors) {
2855                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2856                                 }
2857                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2858                                 first.maybeThinking = TRUE;
2859                             } else {
2860                                 if (first.usePlayother) {
2861                                   if (first.sendTime) {
2862                                     SendTimeRemaining(&first, TRUE);
2863                                   }
2864                                   SendToProgram("playother\n", &first);
2865                                   firstMove = FALSE;
2866                                 } else {
2867                                   firstMove = TRUE;
2868                                 }
2869                             }
2870                         } else if (gameMode == IcsPlayingBlack) {
2871                             if (!WhiteOnMove(forwardMostMove)) {
2872                                 if (first.sendTime) {
2873                                   if (first.useColors) {
2874                                     SendToProgram("white\n", &first);
2875                                   }
2876                                   SendTimeRemaining(&first, FALSE);
2877                                 }
2878                                 if (first.useColors) {
2879                                   SendToProgram("black\n", &first);
2880                                 }
2881                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2882                                 first.maybeThinking = TRUE;
2883                             } else {
2884                                 if (first.usePlayother) {
2885                                   if (first.sendTime) {
2886                                     SendTimeRemaining(&first, FALSE);
2887                                   }
2888                                   SendToProgram("playother\n", &first);
2889                                   firstMove = FALSE;
2890                                 } else {
2891                                   firstMove = TRUE;
2892                                 }
2893                             }
2894                         }                       
2895                     }
2896 #endif
2897                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2898                         /* Moves came from oldmoves or moves command
2899                            while we weren't doing anything else.
2900                            */
2901                         currentMove = forwardMostMove;
2902                         ClearHighlights();/*!!could figure this out*/
2903                         flipView = appData.flipView;
2904                         DrawPosition(FALSE, boards[currentMove]);
2905                         DisplayBothClocks();
2906                         sprintf(str, "%s vs. %s",
2907                                 gameInfo.white, gameInfo.black);
2908                         DisplayTitle(str);
2909                         gameMode = IcsIdle;
2910                     } else {
2911                         /* Moves were history of an active game */
2912                         if (gameInfo.resultDetails != NULL) {
2913                             free(gameInfo.resultDetails);
2914                             gameInfo.resultDetails = NULL;
2915                         }
2916                     }
2917                     HistorySet(parseList, backwardMostMove,
2918                                forwardMostMove, currentMove-1);
2919                     DisplayMove(currentMove - 1);
2920                     if (started == STARTED_MOVES) next_out = i;
2921                     started = STARTED_NONE;
2922                     ics_getting_history = H_FALSE;
2923                     break;
2924
2925                   case STARTED_OBSERVE:
2926                     started = STARTED_NONE;
2927                     SendToICS(ics_prefix);
2928                     SendToICS("refresh\n");
2929                     break;
2930
2931                   default:
2932                     break;
2933                 }
2934                 if(bookHit) { // [HGM] book: simulate book reply
2935                     static char bookMove[MSG_SIZ]; // a bit generous?
2936
2937                     programStats.nodes = programStats.depth = programStats.time = 
2938                     programStats.score = programStats.got_only_move = 0;
2939                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2940
2941                     strcpy(bookMove, "move ");
2942                     strcat(bookMove, bookHit);
2943                     HandleMachineMove(bookMove, &first);
2944                 }
2945                 continue;
2946             }
2947             
2948             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2949                  started == STARTED_HOLDINGS ||
2950                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2951                 /* Accumulate characters in move list or board */
2952                 parse[parse_pos++] = buf[i];
2953             }
2954             
2955             /* Start of game messages.  Mostly we detect start of game
2956                when the first board image arrives.  On some versions
2957                of the ICS, though, we need to do a "refresh" after starting
2958                to observe in order to get the current board right away. */
2959             if (looking_at(buf, &i, "Adding game * to observation list")) {
2960                 started = STARTED_OBSERVE;
2961                 continue;
2962             }
2963
2964             /* Handle auto-observe */
2965             if (appData.autoObserve &&
2966                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2967                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2968                 char *player;
2969                 /* Choose the player that was highlighted, if any. */
2970                 if (star_match[0][0] == '\033' ||
2971                     star_match[1][0] != '\033') {
2972                     player = star_match[0];
2973                 } else {
2974                     player = star_match[2];
2975                 }
2976                 sprintf(str, "%sobserve %s\n",
2977                         ics_prefix, StripHighlightAndTitle(player));
2978                 SendToICS(str);
2979
2980                 /* Save ratings from notify string */
2981                 strcpy(player1Name, star_match[0]);
2982                 player1Rating = string_to_rating(star_match[1]);
2983                 strcpy(player2Name, star_match[2]);
2984                 player2Rating = string_to_rating(star_match[3]);
2985
2986                 if (appData.debugMode)
2987                   fprintf(debugFP, 
2988                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2989                           player1Name, player1Rating,
2990                           player2Name, player2Rating);
2991
2992                 continue;
2993             }
2994
2995             /* Deal with automatic examine mode after a game,
2996                and with IcsObserving -> IcsExamining transition */
2997             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2998                 looking_at(buf, &i, "has made you an examiner of game *")) {
2999
3000                 int gamenum = atoi(star_match[0]);
3001                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3002                     gamenum == ics_gamenum) {
3003                     /* We were already playing or observing this game;
3004                        no need to refetch history */
3005                     gameMode = IcsExamining;
3006                     if (pausing) {
3007                         pauseExamForwardMostMove = forwardMostMove;
3008                     } else if (currentMove < forwardMostMove) {
3009                         ForwardInner(forwardMostMove);
3010                     }
3011                 } else {
3012                     /* I don't think this case really can happen */
3013                     SendToICS(ics_prefix);
3014                     SendToICS("refresh\n");
3015                 }
3016                 continue;
3017             }    
3018             
3019             /* Error messages */
3020 //          if (ics_user_moved) {
3021             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3022                 if (looking_at(buf, &i, "Illegal move") ||
3023                     looking_at(buf, &i, "Not a legal move") ||
3024                     looking_at(buf, &i, "Your king is in check") ||
3025                     looking_at(buf, &i, "It isn't your turn") ||
3026                     looking_at(buf, &i, "It is not your move")) {
3027                     /* Illegal move */
3028                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3029                         currentMove = --forwardMostMove;
3030                         DisplayMove(currentMove - 1); /* before DMError */
3031                         DrawPosition(FALSE, boards[currentMove]);
3032                         SwitchClocks();
3033                         DisplayBothClocks();
3034                     }
3035                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3036                     ics_user_moved = 0;
3037                     continue;
3038                 }
3039             }
3040
3041             if (looking_at(buf, &i, "still have time") ||
3042                 looking_at(buf, &i, "not out of time") ||
3043                 looking_at(buf, &i, "either player is out of time") ||
3044                 looking_at(buf, &i, "has timeseal; checking")) {
3045                 /* We must have called his flag a little too soon */
3046                 whiteFlag = blackFlag = FALSE;
3047                 continue;
3048             }
3049
3050             if (looking_at(buf, &i, "added * seconds to") ||
3051                 looking_at(buf, &i, "seconds were added to")) {
3052                 /* Update the clocks */
3053                 SendToICS(ics_prefix);
3054                 SendToICS("refresh\n");
3055                 continue;
3056             }
3057
3058             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3059                 ics_clock_paused = TRUE;
3060                 StopClocks();
3061                 continue;
3062             }
3063
3064             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3065                 ics_clock_paused = FALSE;
3066                 StartClocks();
3067                 continue;
3068             }
3069
3070             /* Grab player ratings from the Creating: message.
3071                Note we have to check for the special case when
3072                the ICS inserts things like [white] or [black]. */
3073             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3074                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3075                 /* star_matches:
3076                    0    player 1 name (not necessarily white)
3077                    1    player 1 rating
3078                    2    empty, white, or black (IGNORED)
3079                    3    player 2 name (not necessarily black)
3080                    4    player 2 rating
3081                    
3082                    The names/ratings are sorted out when the game
3083                    actually starts (below).
3084                 */
3085                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3086                 player1Rating = string_to_rating(star_match[1]);
3087                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3088                 player2Rating = string_to_rating(star_match[4]);
3089
3090                 if (appData.debugMode)
3091                   fprintf(debugFP, 
3092                           "Ratings from 'Creating:' %s %d, %s %d\n",
3093                           player1Name, player1Rating,
3094                           player2Name, player2Rating);
3095
3096                 continue;
3097             }
3098             
3099             /* Improved generic start/end-of-game messages */
3100             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3101                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3102                 /* If tkind == 0: */
3103                 /* star_match[0] is the game number */
3104                 /*           [1] is the white player's name */
3105                 /*           [2] is the black player's name */
3106                 /* For end-of-game: */
3107                 /*           [3] is the reason for the game end */
3108                 /*           [4] is a PGN end game-token, preceded by " " */
3109                 /* For start-of-game: */
3110                 /*           [3] begins with "Creating" or "Continuing" */
3111                 /*           [4] is " *" or empty (don't care). */
3112                 int gamenum = atoi(star_match[0]);
3113                 char *whitename, *blackname, *why, *endtoken;
3114                 ChessMove endtype = (ChessMove) 0;
3115
3116                 if (tkind == 0) {
3117                   whitename = star_match[1];
3118                   blackname = star_match[2];
3119                   why = star_match[3];
3120                   endtoken = star_match[4];
3121                 } else {
3122                   whitename = star_match[1];
3123                   blackname = star_match[3];
3124                   why = star_match[5];
3125                   endtoken = star_match[6];
3126                 }
3127
3128                 /* Game start messages */
3129                 if (strncmp(why, "Creating ", 9) == 0 ||
3130                     strncmp(why, "Continuing ", 11) == 0) {
3131                     gs_gamenum = gamenum;
3132                     strcpy(gs_kind, strchr(why, ' ') + 1);
3133 #if ZIPPY
3134                     if (appData.zippyPlay) {
3135                         ZippyGameStart(whitename, blackname);
3136                     }
3137 #endif /*ZIPPY*/
3138                     continue;
3139                 }
3140
3141                 /* Game end messages */
3142                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3143                     ics_gamenum != gamenum) {
3144                     continue;
3145                 }
3146                 while (endtoken[0] == ' ') endtoken++;
3147                 switch (endtoken[0]) {
3148                   case '*':
3149                   default:
3150                     endtype = GameUnfinished;
3151                     break;
3152                   case '0':
3153                     endtype = BlackWins;
3154                     break;
3155                   case '1':
3156                     if (endtoken[1] == '/')
3157                       endtype = GameIsDrawn;
3158                     else
3159                       endtype = WhiteWins;
3160                     break;
3161                 }
3162                 GameEnds(endtype, why, GE_ICS);
3163 #if ZIPPY
3164                 if (appData.zippyPlay && first.initDone) {
3165                     ZippyGameEnd(endtype, why);
3166                     if (first.pr == NULL) {
3167                       /* Start the next process early so that we'll
3168                          be ready for the next challenge */
3169                       StartChessProgram(&first);
3170                     }
3171                     /* Send "new" early, in case this command takes
3172                        a long time to finish, so that we'll be ready
3173                        for the next challenge. */
3174                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3175                     Reset(TRUE, TRUE);
3176                 }
3177 #endif /*ZIPPY*/
3178                 continue;
3179             }
3180
3181             if (looking_at(buf, &i, "Removing game * from observation") ||
3182                 looking_at(buf, &i, "no longer observing game *") ||
3183                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3184                 if (gameMode == IcsObserving &&
3185                     atoi(star_match[0]) == ics_gamenum)
3186                   {
3187                       /* icsEngineAnalyze */
3188                       if (appData.icsEngineAnalyze) {
3189                             ExitAnalyzeMode();
3190                             ModeHighlight();
3191                       }
3192                       StopClocks();
3193                       gameMode = IcsIdle;
3194                       ics_gamenum = -1;
3195                       ics_user_moved = FALSE;
3196                   }
3197                 continue;
3198             }
3199
3200             if (looking_at(buf, &i, "no longer examining game *")) {
3201                 if (gameMode == IcsExamining &&
3202                     atoi(star_match[0]) == ics_gamenum)
3203                   {
3204                       gameMode = IcsIdle;
3205                       ics_gamenum = -1;
3206                       ics_user_moved = FALSE;
3207                   }
3208                 continue;
3209             }
3210
3211             /* Advance leftover_start past any newlines we find,
3212                so only partial lines can get reparsed */
3213             if (looking_at(buf, &i, "\n")) {
3214                 prevColor = curColor;
3215                 if (curColor != ColorNormal) {
3216                     if (oldi > next_out) {
3217                         SendToPlayer(&buf[next_out], oldi - next_out);
3218                         next_out = oldi;
3219                     }
3220                     Colorize(ColorNormal, FALSE);
3221                     curColor = ColorNormal;
3222                 }
3223                 if (started == STARTED_BOARD) {
3224                     started = STARTED_NONE;
3225                     parse[parse_pos] = NULLCHAR;
3226                     ParseBoard12(parse);
3227                     ics_user_moved = 0;
3228
3229                     /* Send premove here */
3230                     if (appData.premove) {
3231                       char str[MSG_SIZ];
3232                       if (currentMove == 0 &&
3233                           gameMode == IcsPlayingWhite &&
3234                           appData.premoveWhite) {
3235                         sprintf(str, "%s%s\n", ics_prefix,
3236                                 appData.premoveWhiteText);
3237                         if (appData.debugMode)
3238                           fprintf(debugFP, "Sending premove:\n");
3239                         SendToICS(str);
3240                       } else if (currentMove == 1 &&
3241                                  gameMode == IcsPlayingBlack &&
3242                                  appData.premoveBlack) {
3243                         sprintf(str, "%s%s\n", ics_prefix,
3244                                 appData.premoveBlackText);
3245                         if (appData.debugMode)
3246                           fprintf(debugFP, "Sending premove:\n");
3247                         SendToICS(str);
3248                       } else if (gotPremove) {
3249                         gotPremove = 0;
3250                         ClearPremoveHighlights();
3251                         if (appData.debugMode)
3252                           fprintf(debugFP, "Sending premove:\n");
3253                           UserMoveEvent(premoveFromX, premoveFromY, 
3254                                         premoveToX, premoveToY, 
3255                                         premovePromoChar);
3256                       }
3257                     }
3258
3259                     /* Usually suppress following prompt */
3260                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261                         if (looking_at(buf, &i, "*% ")) {
3262                             savingComment = FALSE;
3263                         }
3264                     }
3265                     next_out = i;
3266                 } else if (started == STARTED_HOLDINGS) {
3267                     int gamenum;
3268                     char new_piece[MSG_SIZ];
3269                     started = STARTED_NONE;
3270                     parse[parse_pos] = NULLCHAR;
3271                     if (appData.debugMode)
3272                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3273                                                         parse, currentMove);
3274                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3275                         gamenum == ics_gamenum) {
3276                         if (gameInfo.variant == VariantNormal) {
3277                           /* [HGM] We seem to switch variant during a game!
3278                            * Presumably no holdings were displayed, so we have
3279                            * to move the position two files to the right to
3280                            * create room for them!
3281                            */
3282                           VariantClass newVariant;
3283                           switch(gameInfo.boardWidth) { // base guess on board width
3284                                 case 9:  newVariant = VariantShogi; break;
3285                                 case 10: newVariant = VariantGreat; break;
3286                                 default: newVariant = VariantCrazyhouse; break;
3287                           }
3288                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3289                           /* Get a move list just to see the header, which
3290                              will tell us whether this is really bug or zh */
3291                           if (ics_getting_history == H_FALSE) {
3292                             ics_getting_history = H_REQUESTED;
3293                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3294                             SendToICS(str);
3295                           }
3296                         }
3297                         new_piece[0] = NULLCHAR;
3298                         sscanf(parse, "game %d white [%s black [%s <- %s",
3299                                &gamenum, white_holding, black_holding,
3300                                new_piece);
3301                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3302                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3303                         /* [HGM] copy holdings to board holdings area */
3304                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3305                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3306 #if ZIPPY
3307                         if (appData.zippyPlay && first.initDone) {
3308                             ZippyHoldings(white_holding, black_holding,
3309                                           new_piece);
3310                         }
3311 #endif /*ZIPPY*/
3312                         if (tinyLayout || smallLayout) {
3313                             char wh[16], bh[16];
3314                             PackHolding(wh, white_holding);
3315                             PackHolding(bh, black_holding);
3316                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3317                                     gameInfo.white, gameInfo.black);
3318                         } else {
3319                             sprintf(str, "%s [%s] vs. %s [%s]",
3320                                     gameInfo.white, white_holding,
3321                                     gameInfo.black, black_holding);
3322                         }
3323
3324                         DrawPosition(FALSE, boards[currentMove]);
3325                         DisplayTitle(str);
3326                     }
3327                     /* Suppress following prompt */
3328                     if (looking_at(buf, &i, "*% ")) {
3329                         savingComment = FALSE;
3330                     }
3331                     next_out = i;
3332                 }
3333                 continue;
3334             }
3335
3336             i++;                /* skip unparsed character and loop back */
3337         }
3338         
3339         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3340             started != STARTED_HOLDINGS && i > next_out) {
3341             SendToPlayer(&buf[next_out], i - next_out);
3342             next_out = i;
3343         }
3344         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3345         
3346         leftover_len = buf_len - leftover_start;
3347         /* if buffer ends with something we couldn't parse,
3348            reparse it after appending the next read */
3349         
3350     } else if (count == 0) {
3351         RemoveInputSource(isr);
3352         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3353     } else {
3354         DisplayFatalError(_("Error reading from ICS"), error, 1);
3355     }
3356 }
3357
3358
3359 /* Board style 12 looks like this:
3360    
3361    <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
3362    
3363  * The "<12> " is stripped before it gets to this routine.  The two
3364  * trailing 0's (flip state and clock ticking) are later addition, and
3365  * some chess servers may not have them, or may have only the first.
3366  * Additional trailing fields may be added in the future.  
3367  */
3368
3369 #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"
3370
3371 #define RELATION_OBSERVING_PLAYED    0
3372 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3373 #define RELATION_PLAYING_MYMOVE      1
3374 #define RELATION_PLAYING_NOTMYMOVE  -1
3375 #define RELATION_EXAMINING           2
3376 #define RELATION_ISOLATED_BOARD     -3
3377 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3378
3379 void
3380 ParseBoard12(string)
3381      char *string;
3382
3383     GameMode newGameMode;
3384     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3385     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3386     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3387     char to_play, board_chars[200];
3388     char move_str[500], str[500], elapsed_time[500];
3389     char black[32], white[32];
3390     Board board;
3391     int prevMove = currentMove;
3392     int ticking = 2;
3393     ChessMove moveType;
3394     int fromX, fromY, toX, toY;
3395     char promoChar;
3396     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3397     char *bookHit = NULL; // [HGM] book
3398     Boolean weird = FALSE;
3399
3400     fromX = fromY = toX = toY = -1;
3401     
3402     newGame = FALSE;
3403
3404     if (appData.debugMode)
3405       fprintf(debugFP, _("Parsing board: %s\n"), string);
3406
3407     move_str[0] = NULLCHAR;
3408     elapsed_time[0] = NULLCHAR;
3409     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3410         int  i = 0, j;
3411         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3412             if(string[i] == ' ') { ranks++; files = 0; }
3413             else files++;
3414             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3415             i++;
3416         }
3417         for(j = 0; j <i; j++) board_chars[j] = string[j];
3418         board_chars[i] = '\0';
3419         string += i + 1;
3420     }
3421     n = sscanf(string, PATTERN, &to_play, &double_push,
3422                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3423                &gamenum, white, black, &relation, &basetime, &increment,
3424                &white_stren, &black_stren, &white_time, &black_time,
3425                &moveNum, str, elapsed_time, move_str, &ics_flip,
3426                &ticking);
3427
3428    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3429                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3430      /* [HGM] We seem to switch variant during a game!
3431       * Try to guess new variant from board size
3432       */
3433           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3434           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3435           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3436           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3437           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3438           if(!weird) newVariant = VariantNormal;
3439           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3440           /* Get a move list just to see the header, which
3441              will tell us whether this is really bug or zh */
3442           if (ics_getting_history == H_FALSE) {
3443             ics_getting_history = H_REQUESTED;
3444             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3445             SendToICS(str);
3446           }
3447     }
3448
3449     if (n < 21) {
3450         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3451         DisplayError(str, 0);
3452         return;
3453     }
3454
3455     /* Convert the move number to internal form */
3456     moveNum = (moveNum - 1) * 2;
3457     if (to_play == 'B') moveNum++;
3458     if (moveNum >= MAX_MOVES) {
3459       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460                         0, 1);
3461       return;
3462     }
3463     
3464     switch (relation) {
3465       case RELATION_OBSERVING_PLAYED:
3466       case RELATION_OBSERVING_STATIC:
3467         if (gamenum == -1) {
3468             /* Old ICC buglet */
3469             relation = RELATION_OBSERVING_STATIC;
3470         }
3471         newGameMode = IcsObserving;
3472         break;
3473       case RELATION_PLAYING_MYMOVE:
3474       case RELATION_PLAYING_NOTMYMOVE:
3475         newGameMode =
3476           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3477             IcsPlayingWhite : IcsPlayingBlack;
3478         break;
3479       case RELATION_EXAMINING:
3480         newGameMode = IcsExamining;
3481         break;
3482       case RELATION_ISOLATED_BOARD:
3483       default:
3484         /* Just display this board.  If user was doing something else,
3485            we will forget about it until the next board comes. */ 
3486         newGameMode = IcsIdle;
3487         break;
3488       case RELATION_STARTING_POSITION:
3489         newGameMode = gameMode;
3490         break;
3491     }
3492     
3493     /* Modify behavior for initial board display on move listing
3494        of wild games.
3495        */
3496     switch (ics_getting_history) {
3497       case H_FALSE:
3498       case H_REQUESTED:
3499         break;
3500       case H_GOT_REQ_HEADER:
3501       case H_GOT_UNREQ_HEADER:
3502         /* This is the initial position of the current game */
3503         gamenum = ics_gamenum;
3504         moveNum = 0;            /* old ICS bug workaround */
3505         if (to_play == 'B') {
3506           startedFromSetupPosition = TRUE;
3507           blackPlaysFirst = TRUE;
3508           moveNum = 1;
3509           if (forwardMostMove == 0) forwardMostMove = 1;
3510           if (backwardMostMove == 0) backwardMostMove = 1;
3511           if (currentMove == 0) currentMove = 1;
3512         }
3513         newGameMode = gameMode;
3514         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3515         break;
3516       case H_GOT_UNWANTED_HEADER:
3517         /* This is an initial board that we don't want */
3518         return;
3519       case H_GETTING_MOVES:
3520         /* Should not happen */
3521         DisplayError(_("Error gathering move list: extra board"), 0);
3522         ics_getting_history = H_FALSE;
3523         return;
3524     }
3525     
3526     /* Take action if this is the first board of a new game, or of a
3527        different game than is currently being displayed.  */
3528     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3529         relation == RELATION_ISOLATED_BOARD) {
3530         
3531         /* Forget the old game and get the history (if any) of the new one */
3532         if (gameMode != BeginningOfGame) {
3533           Reset(TRUE, TRUE);
3534         }
3535         newGame = TRUE;
3536         if (appData.autoRaiseBoard) BoardToTop();
3537         prevMove = -3;
3538         if (gamenum == -1) {
3539             newGameMode = IcsIdle;
3540         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3541                    appData.getMoveList) {
3542             /* Need to get game history */
3543             ics_getting_history = H_REQUESTED;
3544             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3545             SendToICS(str);
3546         }
3547         
3548         /* Initially flip the board to have black on the bottom if playing
3549            black or if the ICS flip flag is set, but let the user change
3550            it with the Flip View button. */
3551         flipView = appData.autoFlipView ? 
3552           (newGameMode == IcsPlayingBlack) || ics_flip :
3553           appData.flipView;
3554         
3555         /* Done with values from previous mode; copy in new ones */
3556         gameMode = newGameMode;
3557         ModeHighlight();
3558         ics_gamenum = gamenum;
3559         if (gamenum == gs_gamenum) {
3560             int klen = strlen(gs_kind);
3561             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3562             sprintf(str, "ICS %s", gs_kind);
3563             gameInfo.event = StrSave(str);
3564         } else {
3565             gameInfo.event = StrSave("ICS game");
3566         }
3567         gameInfo.site = StrSave(appData.icsHost);
3568         gameInfo.date = PGNDate();
3569         gameInfo.round = StrSave("-");
3570         gameInfo.white = StrSave(white);
3571         gameInfo.black = StrSave(black);
3572         timeControl = basetime * 60 * 1000;
3573         timeControl_2 = 0;
3574         timeIncrement = increment * 1000;
3575         movesPerSession = 0;
3576         gameInfo.timeControl = TimeControlTagValue();
3577         VariantSwitch(board, StringToVariant(gameInfo.event) );
3578   if (appData.debugMode) {
3579     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3580     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3581     setbuf(debugFP, NULL);
3582   }
3583
3584         gameInfo.outOfBook = NULL;
3585         
3586         /* Do we have the ratings? */
3587         if (strcmp(player1Name, white) == 0 &&
3588             strcmp(player2Name, black) == 0) {
3589             if (appData.debugMode)
3590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3591                       player1Rating, player2Rating);
3592             gameInfo.whiteRating = player1Rating;
3593             gameInfo.blackRating = player2Rating;
3594         } else if (strcmp(player2Name, white) == 0 &&
3595                    strcmp(player1Name, black) == 0) {
3596             if (appData.debugMode)
3597               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3598                       player2Rating, player1Rating);
3599             gameInfo.whiteRating = player2Rating;
3600             gameInfo.blackRating = player1Rating;
3601         }
3602         player1Name[0] = player2Name[0] = NULLCHAR;
3603
3604         /* Silence shouts if requested */
3605         if (appData.quietPlay &&
3606             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3607             SendToICS(ics_prefix);
3608             SendToICS("set shout 0\n");
3609         }
3610     }
3611     
3612     /* Deal with midgame name changes */
3613     if (!newGame) {
3614         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3615             if (gameInfo.white) free(gameInfo.white);
3616             gameInfo.white = StrSave(white);
3617         }
3618         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3619             if (gameInfo.black) free(gameInfo.black);
3620             gameInfo.black = StrSave(black);
3621         }
3622     }
3623     
3624     /* Throw away game result if anything actually changes in examine mode */
3625     if (gameMode == IcsExamining && !newGame) {
3626         gameInfo.result = GameUnfinished;
3627         if (gameInfo.resultDetails != NULL) {
3628             free(gameInfo.resultDetails);
3629             gameInfo.resultDetails = NULL;
3630         }
3631     }
3632     
3633     /* In pausing && IcsExamining mode, we ignore boards coming
3634        in if they are in a different variation than we are. */
3635     if (pauseExamInvalid) return;
3636     if (pausing && gameMode == IcsExamining) {
3637         if (moveNum <= pauseExamForwardMostMove) {
3638             pauseExamInvalid = TRUE;
3639             forwardMostMove = pauseExamForwardMostMove;
3640             return;
3641         }
3642     }
3643     
3644   if (appData.debugMode) {
3645     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3646   }
3647     /* Parse the board */
3648     for (k = 0; k < ranks; k++) {
3649       for (j = 0; j < files; j++)
3650         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3651       if(gameInfo.holdingsWidth > 1) {
3652            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3653            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3654       }
3655     }
3656     CopyBoard(boards[moveNum], board);
3657     if (moveNum == 0) {
3658         startedFromSetupPosition =
3659           !CompareBoards(board, initialPosition);
3660         if(startedFromSetupPosition)
3661             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3662     }
3663
3664     /* [HGM] Set castling rights. Take the outermost Rooks,
3665        to make it also work for FRC opening positions. Note that board12
3666        is really defective for later FRC positions, as it has no way to
3667        indicate which Rook can castle if they are on the same side of King.
3668        For the initial position we grant rights to the outermost Rooks,
3669        and remember thos rights, and we then copy them on positions
3670        later in an FRC game. This means WB might not recognize castlings with
3671        Rooks that have moved back to their original position as illegal,
3672        but in ICS mode that is not its job anyway.
3673     */
3674     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3675     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3676
3677         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3678             if(board[0][i] == WhiteRook) j = i;
3679         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3680         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3681             if(board[0][i] == WhiteRook) j = i;
3682         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3683         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3684             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3685         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3686         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3687             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3688         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3689
3690         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3692             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694             if(board[BOARD_HEIGHT-1][k] == bKing)
3695                 initialRights[5] = castlingRights[moveNum][5] = k;
3696     } else { int r;
3697         r = castlingRights[moveNum][0] = initialRights[0];
3698         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3699         r = castlingRights[moveNum][1] = initialRights[1];
3700         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3701         r = castlingRights[moveNum][3] = initialRights[3];
3702         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3703         r = castlingRights[moveNum][4] = initialRights[4];
3704         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3705         /* wildcastle kludge: always assume King has rights */
3706         r = castlingRights[moveNum][2] = initialRights[2];
3707         r = castlingRights[moveNum][5] = initialRights[5];
3708     }
3709     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3710     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3711
3712     
3713     if (ics_getting_history == H_GOT_REQ_HEADER ||
3714         ics_getting_history == H_GOT_UNREQ_HEADER) {
3715         /* This was an initial position from a move list, not
3716            the current position */
3717         return;
3718     }
3719     
3720     /* Update currentMove and known move number limits */
3721     newMove = newGame || moveNum > forwardMostMove;
3722
3723     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3724     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3725         takeback = forwardMostMove - moveNum;
3726         for (i = 0; i < takeback; i++) {
3727              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3728              SendToProgram("undo\n", &first);
3729         }
3730     }
3731
3732     if (newGame) {
3733         forwardMostMove = backwardMostMove = currentMove = moveNum;
3734         if (gameMode == IcsExamining && moveNum == 0) {
3735           /* Workaround for ICS limitation: we are not told the wild
3736              type when starting to examine a game.  But if we ask for
3737              the move list, the move list header will tell us */
3738             ics_getting_history = H_REQUESTED;
3739             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3740             SendToICS(str);
3741         }
3742     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3743                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3744         forwardMostMove = moveNum;
3745         if (!pausing || currentMove > forwardMostMove)
3746           currentMove = forwardMostMove;
3747     } else {
3748         /* New part of history that is not contiguous with old part */ 
3749         if (pausing && gameMode == IcsExamining) {
3750             pauseExamInvalid = TRUE;
3751             forwardMostMove = pauseExamForwardMostMove;
3752             return;
3753         }
3754         forwardMostMove = backwardMostMove = currentMove = moveNum;
3755         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3756             ics_getting_history = H_REQUESTED;
3757             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3758             SendToICS(str);
3759         }
3760     }
3761     
3762     /* Update the clocks */
3763     if (strchr(elapsed_time, '.')) {
3764       /* Time is in ms */
3765       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3766       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3767     } else {
3768       /* Time is in seconds */
3769       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3770       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3771     }
3772       
3773
3774 #if ZIPPY
3775     if (appData.zippyPlay && newGame &&
3776         gameMode != IcsObserving && gameMode != IcsIdle &&
3777         gameMode != IcsExamining)
3778       ZippyFirstBoard(moveNum, basetime, increment);
3779 #endif
3780     
3781     /* Put the move on the move list, first converting
3782        to canonical algebraic form. */
3783     if (moveNum > 0) {
3784   if (appData.debugMode) {
3785     if (appData.debugMode) { int f = forwardMostMove;
3786         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3787                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3788     }
3789     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3790     fprintf(debugFP, "moveNum = %d\n", moveNum);
3791     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3792     setbuf(debugFP, NULL);
3793   }
3794         if (moveNum <= backwardMostMove) {
3795             /* We don't know what the board looked like before
3796                this move.  Punt. */
3797             strcpy(parseList[moveNum - 1], move_str);
3798             strcat(parseList[moveNum - 1], " ");
3799             strcat(parseList[moveNum - 1], elapsed_time);
3800             moveList[moveNum - 1][0] = NULLCHAR;
3801         } else if (strcmp(move_str, "none") == 0) {
3802             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3803             /* Again, we don't know what the board looked like;
3804                this is really the start of the game. */
3805             parseList[moveNum - 1][0] = NULLCHAR;
3806             moveList[moveNum - 1][0] = NULLCHAR;
3807             backwardMostMove = moveNum;
3808             startedFromSetupPosition = TRUE;
3809             fromX = fromY = toX = toY = -1;
3810         } else {
3811           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3812           //                 So we parse the long-algebraic move string in stead of the SAN move
3813           int valid; char buf[MSG_SIZ], *prom;
3814
3815           // str looks something like "Q/a1-a2"; kill the slash
3816           if(str[1] == '/') 
3817                 sprintf(buf, "%c%s", str[0], str+2);
3818           else  strcpy(buf, str); // might be castling
3819           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3820                 strcat(buf, prom); // long move lacks promo specification!
3821           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3822                 if(appData.debugMode) 
3823                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3824                 strcpy(move_str, buf);
3825           }
3826           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3827                                 &fromX, &fromY, &toX, &toY, &promoChar)
3828                || ParseOneMove(buf, moveNum - 1, &moveType,
3829                                 &fromX, &fromY, &toX, &toY, &promoChar);
3830           // end of long SAN patch
3831           if (valid) {
3832             (void) CoordsToAlgebraic(boards[moveNum - 1],
3833                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3834                                      fromY, fromX, toY, toX, promoChar,
3835                                      parseList[moveNum-1]);
3836             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3837                              castlingRights[moveNum]) ) {
3838               case MT_NONE:
3839               case MT_STALEMATE:
3840               default:
3841                 break;
3842               case MT_CHECK:
3843                 if(gameInfo.variant != VariantShogi)
3844                     strcat(parseList[moveNum - 1], "+");
3845                 break;
3846               case MT_CHECKMATE:
3847               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3848                 strcat(parseList[moveNum - 1], "#");
3849                 break;
3850             }
3851             strcat(parseList[moveNum - 1], " ");
3852             strcat(parseList[moveNum - 1], elapsed_time);
3853             /* currentMoveString is set as a side-effect of ParseOneMove */
3854             strcpy(moveList[moveNum - 1], currentMoveString);
3855             strcat(moveList[moveNum - 1], "\n");
3856           } else {
3857             /* Move from ICS was illegal!?  Punt. */
3858   if (appData.debugMode) {
3859     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3860     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3861   }
3862             strcpy(parseList[moveNum - 1], move_str);
3863             strcat(parseList[moveNum - 1], " ");
3864             strcat(parseList[moveNum - 1], elapsed_time);
3865             moveList[moveNum - 1][0] = NULLCHAR;
3866             fromX = fromY = toX = toY = -1;
3867           }
3868         }
3869   if (appData.debugMode) {
3870     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3871     setbuf(debugFP, NULL);
3872   }
3873
3874 #if ZIPPY
3875         /* Send move to chess program (BEFORE animating it). */
3876         if (appData.zippyPlay && !newGame && newMove && 
3877            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3878
3879             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3880                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3881                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3882                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3883                             move_str);
3884                     DisplayError(str, 0);
3885                 } else {
3886                     if (first.sendTime) {
3887                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3888                     }
3889                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3890                     if (firstMove && !bookHit) {
3891                         firstMove = FALSE;
3892                         if (first.useColors) {
3893                           SendToProgram(gameMode == IcsPlayingWhite ?
3894                                         "white\ngo\n" :
3895                                         "black\ngo\n", &first);
3896                         } else {
3897                           SendToProgram("go\n", &first);
3898                         }
3899                         first.maybeThinking = TRUE;
3900                     }
3901                 }
3902             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3903               if (moveList[moveNum - 1][0] == NULLCHAR) {
3904                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3905                 DisplayError(str, 0);
3906               } else {
3907                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3908                 SendMoveToProgram(moveNum - 1, &first);
3909               }
3910             }
3911         }
3912 #endif
3913     }
3914
3915     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3916         /* If move comes from a remote source, animate it.  If it
3917            isn't remote, it will have already been animated. */
3918         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3919             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3920         }
3921         if (!pausing && appData.highlightLastMove) {
3922             SetHighlights(fromX, fromY, toX, toY);
3923         }
3924     }
3925     
3926     /* Start the clocks */
3927     whiteFlag = blackFlag = FALSE;
3928     appData.clockMode = !(basetime == 0 && increment == 0);
3929     if (ticking == 0) {
3930       ics_clock_paused = TRUE;
3931       StopClocks();
3932     } else if (ticking == 1) {
3933       ics_clock_paused = FALSE;
3934     }
3935     if (gameMode == IcsIdle ||
3936         relation == RELATION_OBSERVING_STATIC ||
3937         relation == RELATION_EXAMINING ||
3938         ics_clock_paused)
3939       DisplayBothClocks();
3940     else
3941       StartClocks();
3942     
3943     /* Display opponents and material strengths */
3944     if (gameInfo.variant != VariantBughouse &&
3945         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3946         if (tinyLayout || smallLayout) {
3947             if(gameInfo.variant == VariantNormal)
3948                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3949                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3950                     basetime, increment);
3951             else
3952                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3953                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3954                     basetime, increment, (int) gameInfo.variant);
3955         } else {
3956             if(gameInfo.variant == VariantNormal)
3957                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3958                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3959                     basetime, increment);
3960             else
3961                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3962                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3963                     basetime, increment, VariantName(gameInfo.variant));
3964         }
3965         DisplayTitle(str);
3966   if (appData.debugMode) {
3967     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3968   }
3969     }
3970
3971    
3972     /* Display the board */
3973     if (!pausing && !appData.noGUI) {
3974       
3975       if (appData.premove)
3976           if (!gotPremove || 
3977              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3978              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3979               ClearPremoveHighlights();
3980
3981       DrawPosition(FALSE, boards[currentMove]);
3982       DisplayMove(moveNum - 1);
3983       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3984             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3985               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3986         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3987       }
3988     }
3989
3990     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3991 #if ZIPPY
3992     if(bookHit) { // [HGM] book: simulate book reply
3993         static char bookMove[MSG_SIZ]; // a bit generous?
3994
3995         programStats.nodes = programStats.depth = programStats.time = 
3996         programStats.score = programStats.got_only_move = 0;
3997         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3998
3999         strcpy(bookMove, "move ");
4000         strcat(bookMove, bookHit);
4001         HandleMachineMove(bookMove, &first);
4002     }
4003 #endif
4004 }
4005
4006 void
4007 GetMoveListEvent()
4008 {
4009     char buf[MSG_SIZ];
4010     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4011         ics_getting_history = H_REQUESTED;
4012         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4013         SendToICS(buf);
4014     }
4015 }
4016
4017 void
4018 AnalysisPeriodicEvent(force)
4019      int force;
4020 {
4021     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4022          && !force) || !appData.periodicUpdates)
4023       return;
4024
4025     /* Send . command to Crafty to collect stats */
4026     SendToProgram(".\n", &first);
4027
4028     /* Don't send another until we get a response (this makes
4029        us stop sending to old Crafty's which don't understand
4030        the "." command (sending illegal cmds resets node count & time,
4031        which looks bad)) */
4032     programStats.ok_to_send = 0;
4033 }
4034
4035 void ics_update_width(new_width)
4036         int new_width;
4037 {
4038         ics_printf("set width %d\n", new_width);
4039 }
4040
4041 void
4042 SendMoveToProgram(moveNum, cps)
4043      int moveNum;
4044      ChessProgramState *cps;
4045 {
4046     char buf[MSG_SIZ];
4047
4048     if (cps->useUsermove) {
4049       SendToProgram("usermove ", cps);
4050     }
4051     if (cps->useSAN) {
4052       char *space;
4053       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4054         int len = space - parseList[moveNum];
4055         memcpy(buf, parseList[moveNum], len);
4056         buf[len++] = '\n';
4057         buf[len] = NULLCHAR;
4058       } else {
4059         sprintf(buf, "%s\n", parseList[moveNum]);
4060       }
4061       SendToProgram(buf, cps);
4062     } else {
4063       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4064         AlphaRank(moveList[moveNum], 4);
4065         SendToProgram(moveList[moveNum], cps);
4066         AlphaRank(moveList[moveNum], 4); // and back
4067       } else
4068       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4069        * the engine. It would be nice to have a better way to identify castle 
4070        * moves here. */
4071       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4072                                                                          && cps->useOOCastle) {
4073         int fromX = moveList[moveNum][0] - AAA; 
4074         int fromY = moveList[moveNum][1] - ONE;
4075         int toX = moveList[moveNum][2] - AAA; 
4076         int toY = moveList[moveNum][3] - ONE;
4077         if((boards[moveNum][fromY][fromX] == WhiteKing 
4078             && boards[moveNum][toY][toX] == WhiteRook)
4079            || (boards[moveNum][fromY][fromX] == BlackKing 
4080                && boards[moveNum][toY][toX] == BlackRook)) {
4081           if(toX > fromX) SendToProgram("O-O\n", cps);
4082           else SendToProgram("O-O-O\n", cps);
4083         }
4084         else SendToProgram(moveList[moveNum], cps);
4085       }
4086       else SendToProgram(moveList[moveNum], cps);
4087       /* End of additions by Tord */
4088     }
4089
4090     /* [HGM] setting up the opening has brought engine in force mode! */
4091     /*       Send 'go' if we are in a mode where machine should play. */
4092     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4093         (gameMode == TwoMachinesPlay   ||
4094 #ifdef ZIPPY
4095          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4096 #endif
4097          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4098         SendToProgram("go\n", cps);
4099   if (appData.debugMode) {
4100     fprintf(debugFP, "(extra)\n");
4101   }
4102     }
4103     setboardSpoiledMachineBlack = 0;
4104 }
4105
4106 void
4107 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4108      ChessMove moveType;
4109      int fromX, fromY, toX, toY;
4110 {
4111     char user_move[MSG_SIZ];
4112
4113     switch (moveType) {
4114       default:
4115         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4116                 (int)moveType, fromX, fromY, toX, toY);
4117         DisplayError(user_move + strlen("say "), 0);
4118         break;
4119       case WhiteKingSideCastle:
4120       case BlackKingSideCastle:
4121       case WhiteQueenSideCastleWild:
4122       case BlackQueenSideCastleWild:
4123       /* PUSH Fabien */
4124       case WhiteHSideCastleFR:
4125       case BlackHSideCastleFR:
4126       /* POP Fabien */
4127         sprintf(user_move, "o-o\n");
4128         break;
4129       case WhiteQueenSideCastle:
4130       case BlackQueenSideCastle:
4131       case WhiteKingSideCastleWild:
4132       case BlackKingSideCastleWild:
4133       /* PUSH Fabien */
4134       case WhiteASideCastleFR:
4135       case BlackASideCastleFR:
4136       /* POP Fabien */
4137         sprintf(user_move, "o-o-o\n");
4138         break;
4139       case WhitePromotionQueen:
4140       case BlackPromotionQueen:
4141       case WhitePromotionRook:
4142       case BlackPromotionRook:
4143       case WhitePromotionBishop:
4144       case BlackPromotionBishop:
4145       case WhitePromotionKnight:
4146       case BlackPromotionKnight:
4147       case WhitePromotionKing:
4148       case BlackPromotionKing:
4149       case WhitePromotionChancellor:
4150       case BlackPromotionChancellor:
4151       case WhitePromotionArchbishop:
4152       case BlackPromotionArchbishop:
4153         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4154             sprintf(user_move, "%c%c%c%c=%c\n",
4155                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4156                 PieceToChar(WhiteFerz));
4157         else if(gameInfo.variant == VariantGreat)
4158             sprintf(user_move, "%c%c%c%c=%c\n",
4159                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4160                 PieceToChar(WhiteMan));
4161         else
4162             sprintf(user_move, "%c%c%c%c=%c\n",
4163                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4164                 PieceToChar(PromoPiece(moveType)));
4165         break;
4166       case WhiteDrop:
4167       case BlackDrop:
4168         sprintf(user_move, "%c@%c%c\n",
4169                 ToUpper(PieceToChar((ChessSquare) fromX)),
4170                 AAA + toX, ONE + toY);
4171         break;
4172       case NormalMove:
4173       case WhiteCapturesEnPassant:
4174       case BlackCapturesEnPassant:
4175       case IllegalMove:  /* could be a variant we don't quite understand */
4176         sprintf(user_move, "%c%c%c%c\n",
4177                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4178         break;
4179     }
4180     SendToICS(user_move);
4181     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4182         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4183 }
4184
4185 void
4186 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4187      int rf, ff, rt, ft;
4188      char promoChar;
4189      char move[7];
4190 {
4191     if (rf == DROP_RANK) {
4192         sprintf(move, "%c@%c%c\n",
4193                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4194     } else {
4195         if (promoChar == 'x' || promoChar == NULLCHAR) {
4196             sprintf(move, "%c%c%c%c\n",
4197                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4198         } else {
4199             sprintf(move, "%c%c%c%c%c\n",
4200                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4201         }
4202     }
4203 }
4204
4205 void
4206 ProcessICSInitScript(f)
4207      FILE *f;
4208 {
4209     char buf[MSG_SIZ];
4210
4211     while (fgets(buf, MSG_SIZ, f)) {
4212         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4213     }
4214
4215     fclose(f);
4216 }
4217
4218
4219 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4220 void
4221 AlphaRank(char *move, int n)
4222 {
4223 //    char *p = move, c; int x, y;
4224
4225     if (appData.debugMode) {
4226         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4227     }
4228
4229     if(move[1]=='*' && 
4230        move[2]>='0' && move[2]<='9' &&
4231        move[3]>='a' && move[3]<='x'    ) {
4232         move[1] = '@';
4233         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4234         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4235     } else
4236     if(move[0]>='0' && move[0]<='9' &&
4237        move[1]>='a' && move[1]<='x' &&
4238        move[2]>='0' && move[2]<='9' &&
4239        move[3]>='a' && move[3]<='x'    ) {
4240         /* input move, Shogi -> normal */
4241         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4242         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4243         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4244         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4245     } else
4246     if(move[1]=='@' &&
4247        move[3]>='0' && move[3]<='9' &&
4248        move[2]>='a' && move[2]<='x'    ) {
4249         move[1] = '*';
4250         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4251         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4252     } else
4253     if(
4254        move[0]>='a' && move[0]<='x' &&
4255        move[3]>='0' && move[3]<='9' &&
4256        move[2]>='a' && move[2]<='x'    ) {
4257          /* output move, normal -> Shogi */
4258         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4259         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4260         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4261         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4262         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4263     }
4264     if (appData.debugMode) {
4265         fprintf(debugFP, "   out = '%s'\n", move);
4266     }
4267 }
4268
4269 /* Parser for moves from gnuchess, ICS, or user typein box */
4270 Boolean
4271 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4272      char *move;
4273      int moveNum;
4274      ChessMove *moveType;
4275      int *fromX, *fromY, *toX, *toY;
4276      char *promoChar;
4277 {       
4278     if (appData.debugMode) {
4279         fprintf(debugFP, "move to parse: %s\n", move);
4280     }
4281     *moveType = yylexstr(moveNum, move);
4282
4283     switch (*moveType) {
4284       case WhitePromotionChancellor:
4285       case BlackPromotionChancellor:
4286       case WhitePromotionArchbishop:
4287       case BlackPromotionArchbishop:
4288       case WhitePromotionQueen:
4289       case BlackPromotionQueen:
4290       case WhitePromotionRook:
4291       case BlackPromotionRook:
4292       case WhitePromotionBishop:
4293       case BlackPromotionBishop:
4294       case WhitePromotionKnight:
4295       case BlackPromotionKnight:
4296       case WhitePromotionKing:
4297       case BlackPromotionKing:
4298       case NormalMove:
4299       case WhiteCapturesEnPassant:
4300       case BlackCapturesEnPassant:
4301       case WhiteKingSideCastle:
4302       case WhiteQueenSideCastle:
4303       case BlackKingSideCastle:
4304       case BlackQueenSideCastle:
4305       case WhiteKingSideCastleWild:
4306       case WhiteQueenSideCastleWild:
4307       case BlackKingSideCastleWild:
4308       case BlackQueenSideCastleWild:
4309       /* Code added by Tord: */
4310       case WhiteHSideCastleFR:
4311       case WhiteASideCastleFR:
4312       case BlackHSideCastleFR:
4313       case BlackASideCastleFR:
4314       /* End of code added by Tord */
4315       case IllegalMove:         /* bug or odd chess variant */
4316         *fromX = currentMoveString[0] - AAA;
4317         *fromY = currentMoveString[1] - ONE;
4318         *toX = currentMoveString[2] - AAA;
4319         *toY = currentMoveString[3] - ONE;
4320         *promoChar = currentMoveString[4];
4321         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4322             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4323     if (appData.debugMode) {
4324         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4325     }
4326             *fromX = *fromY = *toX = *toY = 0;
4327             return FALSE;
4328         }
4329         if (appData.testLegality) {
4330           return (*moveType != IllegalMove);
4331         } else {
4332           return !(fromX == fromY && toX == toY);
4333         }
4334
4335       case WhiteDrop:
4336       case BlackDrop:
4337         *fromX = *moveType == WhiteDrop ?
4338           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4339           (int) CharToPiece(ToLower(currentMoveString[0]));
4340         *fromY = DROP_RANK;
4341         *toX = currentMoveString[2] - AAA;
4342         *toY = currentMoveString[3] - ONE;
4343         *promoChar = NULLCHAR;
4344         return TRUE;
4345
4346       case AmbiguousMove:
4347       case ImpossibleMove:
4348       case (ChessMove) 0:       /* end of file */
4349       case ElapsedTime:
4350       case Comment:
4351       case PGNTag:
4352       case NAG:
4353       case WhiteWins:
4354       case BlackWins:
4355       case GameIsDrawn:
4356       default:
4357     if (appData.debugMode) {
4358         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4359     }
4360         /* bug? */
4361         *fromX = *fromY = *toX = *toY = 0;
4362         *promoChar = NULLCHAR;
4363         return FALSE;
4364     }
4365 }
4366
4367 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4368 // All positions will have equal probability, but the current method will not provide a unique
4369 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4370 #define DARK 1
4371 #define LITE 2
4372 #define ANY 3
4373
4374 int squaresLeft[4];
4375 int piecesLeft[(int)BlackPawn];
4376 int seed, nrOfShuffles;
4377
4378 void GetPositionNumber()
4379 {       // sets global variable seed
4380         int i;
4381
4382         seed = appData.defaultFrcPosition;
4383         if(seed < 0) { // randomize based on time for negative FRC position numbers
4384                 for(i=0; i<50; i++) seed += random();
4385                 seed = random() ^ random() >> 8 ^ random() << 8;
4386                 if(seed<0) seed = -seed;
4387         }
4388 }
4389
4390 int put(Board board, int pieceType, int rank, int n, int shade)
4391 // put the piece on the (n-1)-th empty squares of the given shade
4392 {
4393         int i;
4394
4395         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4396                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4397                         board[rank][i] = (ChessSquare) pieceType;
4398                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4399                         squaresLeft[ANY]--;
4400                         piecesLeft[pieceType]--; 
4401                         return i;
4402                 }
4403         }
4404         return -1;
4405 }
4406
4407
4408 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4409 // calculate where the next piece goes, (any empty square), and put it there
4410 {
4411         int i;
4412
4413         i = seed % squaresLeft[shade];
4414         nrOfShuffles *= squaresLeft[shade];
4415         seed /= squaresLeft[shade];
4416         put(board, pieceType, rank, i, shade);
4417 }
4418
4419 void AddTwoPieces(Board board, int pieceType, int rank)
4420 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4421 {
4422         int i, n=squaresLeft[ANY], j=n-1, k;
4423
4424         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4425         i = seed % k;  // pick one
4426         nrOfShuffles *= k;
4427         seed /= k;
4428         while(i >= j) i -= j--;
4429         j = n - 1 - j; i += j;
4430         put(board, pieceType, rank, j, ANY);
4431         put(board, pieceType, rank, i, ANY);
4432 }
4433
4434 void SetUpShuffle(Board board, int number)
4435 {
4436         int i, p, first=1;
4437
4438         GetPositionNumber(); nrOfShuffles = 1;
4439
4440         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4441         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4442         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4443
4444         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4445
4446         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4447             p = (int) board[0][i];
4448             if(p < (int) BlackPawn) piecesLeft[p] ++;
4449             board[0][i] = EmptySquare;
4450         }
4451
4452         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4453             // shuffles restricted to allow normal castling put KRR first
4454             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4455                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4456             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4457                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4458             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4459                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4460             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4461                 put(board, WhiteRook, 0, 0, ANY);
4462             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4463         }
4464
4465         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4466             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4467             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4468                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4469                 while(piecesLeft[p] >= 2) {
4470                     AddOnePiece(board, p, 0, LITE);
4471                     AddOnePiece(board, p, 0, DARK);
4472                 }
4473                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4474             }
4475
4476         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4477             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4478             // but we leave King and Rooks for last, to possibly obey FRC restriction
4479             if(p == (int)WhiteRook) continue;
4480             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4481             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4482         }
4483
4484         // now everything is placed, except perhaps King (Unicorn) and Rooks
4485
4486         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4487             // Last King gets castling rights
4488             while(piecesLeft[(int)WhiteUnicorn]) {
4489                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4490                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4491             }
4492
4493             while(piecesLeft[(int)WhiteKing]) {
4494                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4495                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4496             }
4497
4498
4499         } else {
4500             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4501             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4502         }
4503
4504         // Only Rooks can be left; simply place them all
4505         while(piecesLeft[(int)WhiteRook]) {
4506                 i = put(board, WhiteRook, 0, 0, ANY);
4507                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4508                         if(first) {
4509                                 first=0;
4510                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4511                         }
4512                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4513                 }
4514         }
4515         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4516             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4517         }
4518
4519         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4520 }
4521
4522 int SetCharTable( char *table, const char * map )
4523 /* [HGM] moved here from winboard.c because of its general usefulness */
4524 /*       Basically a safe strcpy that uses the last character as King */
4525 {
4526     int result = FALSE; int NrPieces;
4527
4528     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4529                     && NrPieces >= 12 && !(NrPieces&1)) {
4530         int i; /* [HGM] Accept even length from 12 to 34 */
4531
4532         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4533         for( i=0; i<NrPieces/2-1; i++ ) {
4534             table[i] = map[i];
4535             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4536         }
4537         table[(int) WhiteKing]  = map[NrPieces/2-1];
4538         table[(int) BlackKing]  = map[NrPieces-1];
4539
4540         result = TRUE;
4541     }
4542
4543     return result;
4544 }
4545
4546 void Prelude(Board board)
4547 {       // [HGM] superchess: random selection of exo-pieces
4548         int i, j, k; ChessSquare p; 
4549         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4550
4551         GetPositionNumber(); // use FRC position number
4552
4553         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4554             SetCharTable(pieceToChar, appData.pieceToCharTable);
4555             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4556                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4557         }
4558
4559         j = seed%4;                 seed /= 4; 
4560         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4561         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4562         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4563         j = seed%3 + (seed%3 >= j); seed /= 3; 
4564         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4565         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4566         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4567         j = seed%3;                 seed /= 3; 
4568         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4569         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4570         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4571         j = seed%2 + (seed%2 >= j); seed /= 2; 
4572         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4573         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4574         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4575         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4576         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4577         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4578         put(board, exoPieces[0],    0, 0, ANY);
4579         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4580 }
4581
4582 void
4583 InitPosition(redraw)
4584      int redraw;
4585 {
4586     ChessSquare (* pieces)[BOARD_SIZE];
4587     int i, j, pawnRow, overrule,
4588     oldx = gameInfo.boardWidth,
4589     oldy = gameInfo.boardHeight,
4590     oldh = gameInfo.holdingsWidth,
4591     oldv = gameInfo.variant;
4592
4593     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4594
4595     /* [AS] Initialize pv info list [HGM] and game status */
4596     {
4597         for( i=0; i<MAX_MOVES; i++ ) {
4598             pvInfoList[i].depth = 0;
4599             epStatus[i]=EP_NONE;
4600             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4601         }
4602
4603         initialRulePlies = 0; /* 50-move counter start */
4604
4605         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4606         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4607     }
4608
4609     
4610     /* [HGM] logic here is completely changed. In stead of full positions */
4611     /* the initialized data only consist of the two backranks. The switch */
4612     /* selects which one we will use, which is than copied to the Board   */
4613     /* initialPosition, which for the rest is initialized by Pawns and    */
4614     /* empty squares. This initial position is then copied to boards[0],  */
4615     /* possibly after shuffling, so that it remains available.            */
4616
4617     gameInfo.holdingsWidth = 0; /* default board sizes */
4618     gameInfo.boardWidth    = 8;
4619     gameInfo.boardHeight   = 8;
4620     gameInfo.holdingsSize  = 0;
4621     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4622     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4623     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4624
4625     switch (gameInfo.variant) {
4626     case VariantFischeRandom:
4627       shuffleOpenings = TRUE;
4628     default:
4629       pieces = FIDEArray;
4630       break;
4631     case VariantShatranj:
4632       pieces = ShatranjArray;
4633       nrCastlingRights = 0;
4634       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4635       break;
4636     case VariantTwoKings:
4637       pieces = twoKingsArray;
4638       break;
4639     case VariantCapaRandom:
4640       shuffleOpenings = TRUE;
4641     case VariantCapablanca:
4642       pieces = CapablancaArray;
4643       gameInfo.boardWidth = 10;
4644       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4645       break;
4646     case VariantGothic:
4647       pieces = GothicArray;
4648       gameInfo.boardWidth = 10;
4649       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4650       break;
4651     case VariantJanus:
4652       pieces = JanusArray;
4653       gameInfo.boardWidth = 10;
4654       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4655       nrCastlingRights = 6;
4656         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4657         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4658         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4659         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4660         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4661         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4662       break;
4663     case VariantFalcon:
4664       pieces = FalconArray;
4665       gameInfo.boardWidth = 10;
4666       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4667       break;
4668     case VariantXiangqi:
4669       pieces = XiangqiArray;
4670       gameInfo.boardWidth  = 9;
4671       gameInfo.boardHeight = 10;
4672       nrCastlingRights = 0;
4673       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4674       break;
4675     case VariantShogi:
4676       pieces = ShogiArray;
4677       gameInfo.boardWidth  = 9;
4678       gameInfo.boardHeight = 9;
4679       gameInfo.holdingsSize = 7;
4680       nrCastlingRights = 0;
4681       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4682       break;
4683     case VariantCourier:
4684       pieces = CourierArray;
4685       gameInfo.boardWidth  = 12;
4686       nrCastlingRights = 0;
4687       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4688       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4689       break;
4690     case VariantKnightmate:
4691       pieces = KnightmateArray;
4692       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4693       break;
4694     case VariantFairy:
4695       pieces = fairyArray;
4696       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4697       break;
4698     case VariantGreat:
4699       pieces = GreatArray;
4700       gameInfo.boardWidth = 10;
4701       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4702       gameInfo.holdingsSize = 8;
4703       break;
4704     case VariantSuper:
4705       pieces = FIDEArray;
4706       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4707       gameInfo.holdingsSize = 8;
4708       startedFromSetupPosition = TRUE;
4709       break;
4710     case VariantCrazyhouse:
4711     case VariantBughouse:
4712       pieces = FIDEArray;
4713       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4714       gameInfo.holdingsSize = 5;
4715       break;
4716     case VariantWildCastle:
4717       pieces = FIDEArray;
4718       /* !!?shuffle with kings guaranteed to be on d or e file */
4719       shuffleOpenings = 1;
4720       break;
4721     case VariantNoCastle:
4722       pieces = FIDEArray;
4723       nrCastlingRights = 0;
4724       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4725       /* !!?unconstrained back-rank shuffle */
4726       shuffleOpenings = 1;
4727       break;
4728     }
4729
4730     overrule = 0;
4731     if(appData.NrFiles >= 0) {
4732         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4733         gameInfo.boardWidth = appData.NrFiles;
4734     }
4735     if(appData.NrRanks >= 0) {
4736         gameInfo.boardHeight = appData.NrRanks;
4737     }
4738     if(appData.holdingsSize >= 0) {
4739         i = appData.holdingsSize;
4740         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4741         gameInfo.holdingsSize = i;
4742     }
4743     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4744     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4745         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4746
4747     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4748     if(pawnRow < 1) pawnRow = 1;
4749
4750     /* User pieceToChar list overrules defaults */
4751     if(appData.pieceToCharTable != NULL)
4752         SetCharTable(pieceToChar, appData.pieceToCharTable);
4753
4754     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4755
4756         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4757             s = (ChessSquare) 0; /* account holding counts in guard band */
4758         for( i=0; i<BOARD_HEIGHT; i++ )
4759             initialPosition[i][j] = s;
4760
4761         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4762         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4763         initialPosition[pawnRow][j] = WhitePawn;
4764         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4765         if(gameInfo.variant == VariantXiangqi) {
4766             if(j&1) {
4767                 initialPosition[pawnRow][j] = 
4768                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4769                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4770                    initialPosition[2][j] = WhiteCannon;
4771                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4772                 }
4773             }
4774         }
4775         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4776     }
4777     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4778
4779             j=BOARD_LEFT+1;
4780             initialPosition[1][j] = WhiteBishop;
4781             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4782             j=BOARD_RGHT-2;
4783             initialPosition[1][j] = WhiteRook;
4784             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4785     }
4786
4787     if( nrCastlingRights == -1) {
4788         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4789         /*       This sets default castling rights from none to normal corners   */
4790         /* Variants with other castling rights must set them themselves above    */
4791         nrCastlingRights = 6;
4792        
4793         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4794         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4795         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4796         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4797         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4798         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4799      }
4800
4801      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4802      if(gameInfo.variant == VariantGreat) { // promotion commoners
4803         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4804         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4805         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4806         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4807      }
4808   if (appData.debugMode) {
4809     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4810   }
4811     if(shuffleOpenings) {
4812         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4813         startedFromSetupPosition = TRUE;
4814     }
4815     if(startedFromPositionFile) {
4816       /* [HGM] loadPos: use PositionFile for every new game */
4817       CopyBoard(initialPosition, filePosition);
4818       for(i=0; i<nrCastlingRights; i++)
4819           castlingRights[0][i] = initialRights[i] = fileRights[i];
4820       startedFromSetupPosition = TRUE;
4821     }
4822
4823     CopyBoard(boards[0], initialPosition);
4824
4825     if(oldx != gameInfo.boardWidth ||
4826        oldy != gameInfo.boardHeight ||
4827        oldh != gameInfo.holdingsWidth
4828 #ifdef GOTHIC
4829        || oldv == VariantGothic ||        // For licensing popups
4830        gameInfo.variant == VariantGothic
4831 #endif
4832 #ifdef FALCON
4833        || oldv == VariantFalcon ||
4834        gameInfo.variant == VariantFalcon
4835 #endif
4836                                          )
4837             InitDrawingSizes(-2 ,0);
4838
4839     if (redraw)
4840       DrawPosition(TRUE, boards[currentMove]);
4841 }
4842
4843 void
4844 SendBoard(cps, moveNum)
4845      ChessProgramState *cps;
4846      int moveNum;
4847 {
4848     char message[MSG_SIZ];
4849     
4850     if (cps->useSetboard) {
4851       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4852       sprintf(message, "setboard %s\n", fen);
4853       SendToProgram(message, cps);
4854       free(fen);
4855
4856     } else {
4857       ChessSquare *bp;
4858       int i, j;
4859       /* Kludge to set black to move, avoiding the troublesome and now
4860        * deprecated "black" command.
4861        */
4862       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4863
4864       SendToProgram("edit\n", cps);
4865       SendToProgram("#\n", cps);
4866       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867         bp = &boards[moveNum][i][BOARD_LEFT];
4868         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869           if ((int) *bp < (int) BlackPawn) {
4870             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4871                     AAA + j, ONE + i);
4872             if(message[0] == '+' || message[0] == '~') {
4873                 sprintf(message, "%c%c%c+\n",
4874                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4875                         AAA + j, ONE + i);
4876             }
4877             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4878                 message[1] = BOARD_RGHT   - 1 - j + '1';
4879                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4880             }
4881             SendToProgram(message, cps);
4882           }
4883         }
4884       }
4885     
4886       SendToProgram("c\n", cps);
4887       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4888         bp = &boards[moveNum][i][BOARD_LEFT];
4889         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4890           if (((int) *bp != (int) EmptySquare)
4891               && ((int) *bp >= (int) BlackPawn)) {
4892             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4893                     AAA + j, ONE + i);
4894             if(message[0] == '+' || message[0] == '~') {
4895                 sprintf(message, "%c%c%c+\n",
4896                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4897                         AAA + j, ONE + i);
4898             }
4899             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4900                 message[1] = BOARD_RGHT   - 1 - j + '1';
4901                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4902             }
4903             SendToProgram(message, cps);
4904           }
4905         }
4906       }
4907     
4908       SendToProgram(".\n", cps);
4909     }
4910     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4911 }
4912
4913 int
4914 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4915 {
4916     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4917     /* [HGM] add Shogi promotions */
4918     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4919     ChessSquare piece;
4920     ChessMove moveType;
4921     Boolean premove;
4922
4923     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4924     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4925
4926     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4927       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4928         return FALSE;
4929
4930     piece = boards[currentMove][fromY][fromX];
4931     if(gameInfo.variant == VariantShogi) {
4932         promotionZoneSize = 3;
4933         highestPromotingPiece = (int)WhiteFerz;
4934     }
4935
4936     // next weed out all moves that do not touch the promotion zone at all
4937     if((int)piece >= BlackPawn) {
4938         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4939              return FALSE;
4940         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4941     } else {
4942         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4943            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4944     }
4945
4946     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4947
4948     // weed out mandatory Shogi promotions
4949     if(gameInfo.variant == VariantShogi) {
4950         if(piece >= BlackPawn) {
4951             if(toY == 0 && piece == BlackPawn ||
4952                toY == 0 && piece == BlackQueen ||
4953                toY <= 1 && piece == BlackKnight) {
4954                 *promoChoice = '+';
4955                 return FALSE;
4956             }
4957         } else {
4958             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4959                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4960                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4961                 *promoChoice = '+';
4962                 return FALSE;
4963             }
4964         }
4965     }
4966
4967     // weed out obviously illegal Pawn moves
4968     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4969         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4970         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4971         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4972         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4973         // note we are not allowed to test for valid (non-)capture, due to premove
4974     }
4975
4976     // we either have a choice what to promote to, or (in Shogi) whether to promote
4977     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4978         *promoChoice = PieceToChar(BlackFerz);  // no choice
4979         return FALSE;
4980     }
4981     if(appData.alwaysPromoteToQueen) { // predetermined
4982         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4983              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4984         else *promoChoice = PieceToChar(BlackQueen);
4985         return FALSE;
4986     }
4987
4988     // suppress promotion popup on illegal moves that are not premoves
4989     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4990               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
4991     if(appData.testLegality && !premove) {
4992         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4993                         epStatus[currentMove], castlingRights[currentMove],
4994                         fromY, fromX, toY, toX, NULLCHAR);
4995         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
4996            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4997             return FALSE;
4998     }
4999
5000     return TRUE;
5001 }
5002
5003 int
5004 InPalace(row, column)
5005      int row, column;
5006 {   /* [HGM] for Xiangqi */
5007     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5008          column < (BOARD_WIDTH + 4)/2 &&
5009          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5010     return FALSE;
5011 }
5012
5013 int
5014 PieceForSquare (x, y)
5015      int x;
5016      int y;
5017 {
5018   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5019      return -1;
5020   else
5021      return boards[currentMove][y][x];
5022 }
5023
5024 int
5025 OKToStartUserMove(x, y)
5026      int x, y;
5027 {
5028     ChessSquare from_piece;
5029     int white_piece;
5030
5031     if (matchMode) return FALSE;
5032     if (gameMode == EditPosition) return TRUE;
5033
5034     if (x >= 0 && y >= 0)
5035       from_piece = boards[currentMove][y][x];
5036     else
5037       from_piece = EmptySquare;
5038
5039     if (from_piece == EmptySquare) return FALSE;
5040
5041     white_piece = (int)from_piece >= (int)WhitePawn &&
5042       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5043
5044     switch (gameMode) {
5045       case PlayFromGameFile:
5046       case AnalyzeFile:
5047       case TwoMachinesPlay:
5048       case EndOfGame:
5049         return FALSE;
5050
5051       case IcsObserving:
5052       case IcsIdle:
5053         return FALSE;
5054
5055       case MachinePlaysWhite:
5056       case IcsPlayingBlack:
5057         if (appData.zippyPlay) return FALSE;
5058         if (white_piece) {
5059             DisplayMoveError(_("You are playing Black"));
5060             return FALSE;
5061         }
5062         break;
5063
5064       case MachinePlaysBlack:
5065       case IcsPlayingWhite:
5066         if (appData.zippyPlay) return FALSE;
5067         if (!white_piece) {
5068             DisplayMoveError(_("You are playing White"));
5069             return FALSE;
5070         }
5071         break;
5072
5073       case EditGame:
5074         if (!white_piece && WhiteOnMove(currentMove)) {
5075             DisplayMoveError(_("It is White's turn"));
5076             return FALSE;
5077         }           
5078         if (white_piece && !WhiteOnMove(currentMove)) {
5079             DisplayMoveError(_("It is Black's turn"));
5080             return FALSE;
5081         }           
5082         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5083             /* Editing correspondence game history */
5084             /* Could disallow this or prompt for confirmation */
5085             cmailOldMove = -1;
5086         }
5087         if (currentMove < forwardMostMove) {
5088             /* Discarding moves */
5089             /* Could prompt for confirmation here,
5090                but I don't think that's such a good idea */
5091             forwardMostMove = currentMove;
5092         }
5093         break;
5094
5095       case BeginningOfGame:
5096         if (appData.icsActive) return FALSE;
5097         if (!appData.noChessProgram) {
5098             if (!white_piece) {
5099                 DisplayMoveError(_("You are playing White"));
5100                 return FALSE;
5101             }
5102         }
5103         break;
5104         
5105       case Training:
5106         if (!white_piece && WhiteOnMove(currentMove)) {
5107             DisplayMoveError(_("It is White's turn"));
5108             return FALSE;
5109         }           
5110         if (white_piece && !WhiteOnMove(currentMove)) {
5111             DisplayMoveError(_("It is Black's turn"));
5112             return FALSE;
5113         }           
5114         break;
5115
5116       default:
5117       case IcsExamining:
5118         break;
5119     }
5120     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5121         && gameMode != AnalyzeFile && gameMode != Training) {
5122         DisplayMoveError(_("Displayed position is not current"));
5123         return FALSE;
5124     }
5125     return TRUE;
5126 }
5127
5128 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5129 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5130 int lastLoadGameUseList = FALSE;
5131 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5132 ChessMove lastLoadGameStart = (ChessMove) 0;
5133
5134 ChessMove
5135 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5136      int fromX, fromY, toX, toY;
5137      int promoChar;
5138      Boolean captureOwn;
5139 {
5140     ChessMove moveType;
5141     ChessSquare pdown, pup;
5142
5143     /* Check if the user is playing in turn.  This is complicated because we
5144        let the user "pick up" a piece before it is his turn.  So the piece he
5145        tried to pick up may have been captured by the time he puts it down!
5146        Therefore we use the color the user is supposed to be playing in this
5147        test, not the color of the piece that is currently on the starting
5148        square---except in EditGame mode, where the user is playing both
5149        sides; fortunately there the capture race can't happen.  (It can
5150        now happen in IcsExamining mode, but that's just too bad.  The user
5151        will get a somewhat confusing message in that case.)
5152        */
5153
5154     switch (gameMode) {
5155       case PlayFromGameFile:
5156       case AnalyzeFile:
5157       case TwoMachinesPlay:
5158       case EndOfGame:
5159       case IcsObserving:
5160       case IcsIdle:
5161         /* We switched into a game mode where moves are not accepted,
5162            perhaps while the mouse button was down. */
5163         return ImpossibleMove;
5164
5165       case MachinePlaysWhite:
5166         /* User is moving for Black */
5167         if (WhiteOnMove(currentMove)) {
5168             DisplayMoveError(_("It is White's turn"));
5169             return ImpossibleMove;
5170         }
5171         break;
5172
5173       case MachinePlaysBlack:
5174         /* User is moving for White */
5175         if (!WhiteOnMove(currentMove)) {
5176             DisplayMoveError(_("It is Black's turn"));
5177             return ImpossibleMove;
5178         }
5179         break;
5180
5181       case EditGame:
5182       case IcsExamining:
5183       case BeginningOfGame:
5184       case AnalyzeMode:
5185       case Training:
5186         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5187             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5188             /* User is moving for Black */
5189             if (WhiteOnMove(currentMove)) {
5190                 DisplayMoveError(_("It is White's turn"));
5191                 return ImpossibleMove;
5192             }
5193         } else {
5194             /* User is moving for White */
5195             if (!WhiteOnMove(currentMove)) {
5196                 DisplayMoveError(_("It is Black's turn"));
5197                 return ImpossibleMove;
5198             }
5199         }
5200         break;
5201
5202       case IcsPlayingBlack:
5203         /* User is moving for Black */
5204         if (WhiteOnMove(currentMove)) {
5205             if (!appData.premove) {
5206                 DisplayMoveError(_("It is White's turn"));
5207             } else if (toX >= 0 && toY >= 0) {
5208                 premoveToX = toX;
5209                 premoveToY = toY;
5210                 premoveFromX = fromX;
5211                 premoveFromY = fromY;
5212                 premovePromoChar = promoChar;
5213                 gotPremove = 1;
5214                 if (appData.debugMode) 
5215                     fprintf(debugFP, "Got premove: fromX %d,"
5216                             "fromY %d, toX %d, toY %d\n",
5217                             fromX, fromY, toX, toY);
5218             }
5219             return ImpossibleMove;
5220         }
5221         break;
5222
5223       case IcsPlayingWhite:
5224         /* User is moving for White */
5225         if (!WhiteOnMove(currentMove)) {
5226             if (!appData.premove) {
5227                 DisplayMoveError(_("It is Black's turn"));
5228             } else if (toX >= 0 && toY >= 0) {
5229                 premoveToX = toX;
5230                 premoveToY = toY;
5231                 premoveFromX = fromX;
5232                 premoveFromY = fromY;
5233                 premovePromoChar = promoChar;
5234                 gotPremove = 1;
5235                 if (appData.debugMode) 
5236                     fprintf(debugFP, "Got premove: fromX %d,"
5237                             "fromY %d, toX %d, toY %d\n",
5238                             fromX, fromY, toX, toY);
5239             }
5240             return ImpossibleMove;
5241         }
5242         break;
5243
5244       default:
5245         break;
5246
5247       case EditPosition:
5248         /* EditPosition, empty square, or different color piece;
5249            click-click move is possible */
5250         if (toX == -2 || toY == -2) {
5251             boards[0][fromY][fromX] = EmptySquare;
5252             return AmbiguousMove;
5253         } else if (toX >= 0 && toY >= 0) {
5254             boards[0][toY][toX] = boards[0][fromY][fromX];
5255             boards[0][fromY][fromX] = EmptySquare;
5256             return AmbiguousMove;
5257         }
5258         return ImpossibleMove;
5259     }
5260
5261     pdown = boards[currentMove][fromY][fromX];
5262     pup = boards[currentMove][toY][toX];
5263
5264     /* [HGM] If move started in holdings, it means a drop */
5265     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5266          if( pup != EmptySquare ) return ImpossibleMove;
5267          if(appData.testLegality) {
5268              /* it would be more logical if LegalityTest() also figured out
5269               * which drops are legal. For now we forbid pawns on back rank.
5270               * Shogi is on its own here...
5271               */
5272              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5273                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5274                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5275          }
5276          return WhiteDrop; /* Not needed to specify white or black yet */
5277     }
5278
5279     userOfferedDraw = FALSE;
5280         
5281     /* [HGM] always test for legality, to get promotion info */
5282     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5283                           epStatus[currentMove], castlingRights[currentMove],
5284                                          fromY, fromX, toY, toX, promoChar);
5285     /* [HGM] but possibly ignore an IllegalMove result */
5286     if (appData.testLegality) {
5287         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5288             DisplayMoveError(_("Illegal move"));
5289             return ImpossibleMove;
5290         }
5291     }
5292 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5293     return moveType;
5294     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5295        function is made into one that returns an OK move type if FinishMove
5296        should be called. This to give the calling driver routine the
5297        opportunity to finish the userMove input with a promotion popup,
5298        without bothering the user with this for invalid or illegal moves */
5299
5300 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5301 }
5302
5303 /* Common tail of UserMoveEvent and DropMenuEvent */
5304 int
5305 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5306      ChessMove moveType;
5307      int fromX, fromY, toX, toY;
5308      /*char*/int promoChar;
5309 {
5310     char *bookHit = 0;
5311 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5312     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5313         // [HGM] superchess: suppress promotions to non-available piece
5314         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5315         if(WhiteOnMove(currentMove)) {
5316             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5317         } else {
5318             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5319         }
5320     }
5321
5322     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5323        move type in caller when we know the move is a legal promotion */
5324     if(moveType == NormalMove && promoChar)
5325         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5326 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5327     /* [HGM] convert drag-and-drop piece drops to standard form */
5328     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5329          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5330            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5331                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5332 //         fromX = boards[currentMove][fromY][fromX];
5333            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5334            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5335            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5336            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5337          fromY = DROP_RANK;
5338     }
5339
5340     /* [HGM] <popupFix> The following if has been moved here from
5341        UserMoveEvent(). Because it seemed to belon here (why not allow
5342        piece drops in training games?), and because it can only be
5343        performed after it is known to what we promote. */
5344     if (gameMode == Training) {
5345       /* compare the move played on the board to the next move in the
5346        * game. If they match, display the move and the opponent's response. 
5347        * If they don't match, display an error message.
5348        */
5349       int saveAnimate;
5350       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5351       CopyBoard(testBoard, boards[currentMove]);
5352       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5353
5354       if (CompareBoards(testBoard, boards[currentMove+1])) {
5355         ForwardInner(currentMove+1);
5356
5357         /* Autoplay the opponent's response.
5358          * if appData.animate was TRUE when Training mode was entered,
5359          * the response will be animated.
5360          */
5361         saveAnimate = appData.animate;
5362         appData.animate = animateTraining;
5363         ForwardInner(currentMove+1);
5364         appData.animate = saveAnimate;
5365
5366         /* check for the end of the game */
5367         if (currentMove >= forwardMostMove) {
5368           gameMode = PlayFromGameFile;
5369           ModeHighlight();
5370           SetTrainingModeOff();
5371           DisplayInformation(_("End of game"));
5372         }
5373       } else {
5374         DisplayError(_("Incorrect move"), 0);
5375       }
5376       return 1;
5377     }
5378
5379   /* Ok, now we know that the move is good, so we can kill
5380      the previous line in Analysis Mode */
5381   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5382     forwardMostMove = currentMove;
5383   }
5384
5385   /* If we need the chess program but it's dead, restart it */
5386   ResurrectChessProgram();
5387
5388   /* A user move restarts a paused game*/
5389   if (pausing)
5390     PauseEvent();
5391
5392   thinkOutput[0] = NULLCHAR;
5393
5394   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5395
5396   if (gameMode == BeginningOfGame) {
5397     if (appData.noChessProgram) {
5398       gameMode = EditGame;
5399       SetGameInfo();
5400     } else {
5401       char buf[MSG_SIZ];
5402       gameMode = MachinePlaysBlack;
5403       StartClocks();
5404       SetGameInfo();
5405       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5406       DisplayTitle(buf);
5407       if (first.sendName) {
5408         sprintf(buf, "name %s\n", gameInfo.white);
5409         SendToProgram(buf, &first);
5410       }
5411       StartClocks();
5412     }
5413     ModeHighlight();
5414   }
5415 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5416   /* Relay move to ICS or chess engine */
5417   if (appData.icsActive) {
5418     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5419         gameMode == IcsExamining) {
5420       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5421       ics_user_moved = 1;
5422     }
5423   } else {
5424     if (first.sendTime && (gameMode == BeginningOfGame ||
5425                            gameMode == MachinePlaysWhite ||
5426                            gameMode == MachinePlaysBlack)) {
5427       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5428     }
5429     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5430          // [HGM] book: if program might be playing, let it use book
5431         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5432         first.maybeThinking = TRUE;
5433     } else SendMoveToProgram(forwardMostMove-1, &first);
5434     if (currentMove == cmailOldMove + 1) {
5435       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5436     }
5437   }
5438
5439   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5440
5441   switch (gameMode) {
5442   case EditGame:
5443     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5444                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5445     case MT_NONE:
5446     case MT_CHECK:
5447       break;
5448     case MT_CHECKMATE:
5449     case MT_STAINMATE:
5450       if (WhiteOnMove(currentMove)) {
5451         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5452       } else {
5453         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5454       }
5455       break;
5456     case MT_STALEMATE:
5457       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5458       break;
5459     }
5460     break;
5461     
5462   case MachinePlaysBlack:
5463   case MachinePlaysWhite:
5464     /* disable certain menu options while machine is thinking */
5465     SetMachineThinkingEnables();
5466     break;
5467
5468   default:
5469     break;
5470   }
5471
5472   if(bookHit) { // [HGM] book: simulate book reply
5473         static char bookMove[MSG_SIZ]; // a bit generous?
5474
5475         programStats.nodes = programStats.depth = programStats.time = 
5476         programStats.score = programStats.got_only_move = 0;
5477         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5478
5479         strcpy(bookMove, "move ");
5480         strcat(bookMove, bookHit);
5481         HandleMachineMove(bookMove, &first);
5482   }
5483   return 1;
5484 }
5485
5486 void
5487 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5488      int fromX, fromY, toX, toY;
5489      int promoChar;
5490 {
5491     /* [HGM] This routine was added to allow calling of its two logical
5492        parts from other modules in the old way. Before, UserMoveEvent()
5493        automatically called FinishMove() if the move was OK, and returned
5494        otherwise. I separated the two, in order to make it possible to
5495        slip a promotion popup in between. But that it always needs two
5496        calls, to the first part, (now called UserMoveTest() ), and to
5497        FinishMove if the first part succeeded. Calls that do not need
5498        to do anything in between, can call this routine the old way. 
5499     */
5500     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5501 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5502     if(moveType == AmbiguousMove)
5503         DrawPosition(FALSE, boards[currentMove]);
5504     else if(moveType != ImpossibleMove && moveType != Comment)
5505         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5506 }
5507
5508 void LeftClick(ClickType clickType, int xPix, int yPix)
5509 {
5510     int x, y;
5511     Boolean saveAnimate;
5512     static int second = 0, promotionChoice = 0;
5513     char promoChoice = NULLCHAR;
5514
5515     if (clickType == Press) ErrorPopDown();
5516
5517     x = EventToSquare(xPix, BOARD_WIDTH);
5518     y = EventToSquare(yPix, BOARD_HEIGHT);
5519     if (!flipView && y >= 0) {
5520         y = BOARD_HEIGHT - 1 - y;
5521     }
5522     if (flipView && x >= 0) {
5523         x = BOARD_WIDTH - 1 - x;
5524     }
5525
5526     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5527         if(clickType == Release) return; // ignore upclick of click-click destination
5528         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5529         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5530         if(gameInfo.holdingsWidth && 
5531                 (WhiteOnMove(currentMove) 
5532                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5533                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5534             // click in right holdings, for determining promotion piece
5535             ChessSquare p = boards[currentMove][y][x];
5536             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5537             if(p != EmptySquare) {
5538                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5539                 fromX = fromY = -1;
5540                 return;
5541             }
5542         }
5543         DrawPosition(FALSE, boards[currentMove]);
5544         return;
5545     }
5546
5547     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5548     if(clickType == Press
5549             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5550               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5551               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5552         return;
5553
5554     if (fromX == -1) {
5555         if (clickType == Press) {
5556             /* First square */
5557             if (OKToStartUserMove(x, y)) {
5558                 fromX = x;
5559                 fromY = y;
5560                 second = 0;
5561                 DragPieceBegin(xPix, yPix);
5562                 if (appData.highlightDragging) {
5563                     SetHighlights(x, y, -1, -1);
5564                 }
5565             }
5566         }
5567         return;
5568     }
5569
5570     /* fromX != -1 */
5571     if (clickType == Press && gameMode != EditPosition) {
5572         ChessSquare fromP;
5573         ChessSquare toP;
5574         int frc;
5575
5576         // ignore off-board to clicks
5577         if(y < 0 || x < 0) return;
5578
5579         /* Check if clicking again on the same color piece */
5580         fromP = boards[currentMove][fromY][fromX];
5581         toP = boards[currentMove][y][x];
5582         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5583         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5584              WhitePawn <= toP && toP <= WhiteKing &&
5585              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5586              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5587             (BlackPawn <= fromP && fromP <= BlackKing && 
5588              BlackPawn <= toP && toP <= BlackKing &&
5589              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5590              !(fromP == BlackKing && toP == BlackRook && frc))) {
5591             /* Clicked again on same color piece -- changed his mind */
5592             second = (x == fromX && y == fromY);
5593             if (appData.highlightDragging) {
5594                 SetHighlights(x, y, -1, -1);
5595             } else {
5596                 ClearHighlights();
5597             }
5598             if (OKToStartUserMove(x, y)) {
5599                 fromX = x;
5600                 fromY = y;
5601                 DragPieceBegin(xPix, yPix);
5602             }
5603             return;
5604         }
5605         // ignore to-clicks in holdings
5606         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5607     }
5608
5609     if (clickType == Release && (x == fromX && y == fromY ||
5610         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5611
5612         // treat drags into holding as click on start square
5613         x = fromX; y = fromY;
5614
5615         DragPieceEnd(xPix, yPix);
5616         if (appData.animateDragging) {
5617             /* Undo animation damage if any */
5618             DrawPosition(FALSE, NULL);
5619         }
5620         if (second) {
5621             /* Second up/down in same square; just abort move */
5622             second = 0;
5623             fromX = fromY = -1;
5624             ClearHighlights();
5625             gotPremove = 0;
5626             ClearPremoveHighlights();
5627         } else {
5628             /* First upclick in same square; start click-click mode */
5629             SetHighlights(x, y, -1, -1);
5630         }
5631         return;
5632     }
5633
5634     /* we now have a different from- and to-square */
5635     /* Completed move */
5636     toX = x;
5637     toY = y;
5638     saveAnimate = appData.animate;
5639     if (clickType == Press) {
5640         /* Finish clickclick move */
5641         if (appData.animate || appData.highlightLastMove) {
5642             SetHighlights(fromX, fromY, toX, toY);
5643         } else {
5644             ClearHighlights();
5645         }
5646     } else {
5647         /* Finish drag move */
5648         if (appData.highlightLastMove) {
5649             SetHighlights(fromX, fromY, toX, toY);
5650         } else {
5651             ClearHighlights();
5652         }
5653         DragPieceEnd(xPix, yPix);
5654         /* Don't animate move and drag both */
5655         appData.animate = FALSE;
5656     }
5657     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5658         SetHighlights(fromX, fromY, toX, toY);
5659         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5660             // [HGM] super: promotion to captured piece selected from holdings
5661             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5662             promotionChoice = TRUE;
5663             // kludge follows to temporarily execute move on display, without promoting yet
5664             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5665             boards[currentMove][toY][toX] = p;
5666             DrawPosition(FALSE, boards[currentMove]);
5667             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5668             boards[currentMove][toY][toX] = q;
5669             DisplayMessage("Click in holdings to choose piece", "");
5670             return;
5671         }
5672         PromotionPopUp();
5673     } else {
5674         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5675         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5676         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5677         fromX = fromY = -1;
5678     }
5679     appData.animate = saveAnimate;
5680     if (appData.animate || appData.animateDragging) {
5681         /* Undo animation damage if needed */
5682         DrawPosition(FALSE, NULL);
5683     }
5684 }
5685
5686 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5687 {
5688 //    char * hint = lastHint;
5689     FrontEndProgramStats stats;
5690
5691     stats.which = cps == &first ? 0 : 1;
5692     stats.depth = cpstats->depth;
5693     stats.nodes = cpstats->nodes;
5694     stats.score = cpstats->score;
5695     stats.time = cpstats->time;
5696     stats.pv = cpstats->movelist;
5697     stats.hint = lastHint;
5698     stats.an_move_index = 0;
5699     stats.an_move_count = 0;
5700
5701     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5702         stats.hint = cpstats->move_name;
5703         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5704         stats.an_move_count = cpstats->nr_moves;
5705     }
5706
5707     SetProgramStats( &stats );
5708 }
5709
5710 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5711 {   // [HGM] book: this routine intercepts moves to simulate book replies
5712     char *bookHit = NULL;
5713
5714     //first determine if the incoming move brings opponent into his book
5715     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5716         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5717     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5718     if(bookHit != NULL && !cps->bookSuspend) {
5719         // make sure opponent is not going to reply after receiving move to book position
5720         SendToProgram("force\n", cps);
5721         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5722     }
5723     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5724     // now arrange restart after book miss
5725     if(bookHit) {
5726         // after a book hit we never send 'go', and the code after the call to this routine
5727         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5728         char buf[MSG_SIZ];
5729         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5730         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5731         SendToProgram(buf, cps);
5732         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5733     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5734         SendToProgram("go\n", cps);
5735         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5736     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5737         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5738             SendToProgram("go\n", cps); 
5739         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5740     }
5741     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5742 }
5743
5744 char *savedMessage;
5745 ChessProgramState *savedState;
5746 void DeferredBookMove(void)
5747 {
5748         if(savedState->lastPing != savedState->lastPong)
5749                     ScheduleDelayedEvent(DeferredBookMove, 10);
5750         else
5751         HandleMachineMove(savedMessage, savedState);
5752 }
5753
5754 void
5755 HandleMachineMove(message, cps)
5756      char *message;
5757      ChessProgramState *cps;
5758 {
5759     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5760     char realname[MSG_SIZ];
5761     int fromX, fromY, toX, toY;
5762     ChessMove moveType;
5763     char promoChar;
5764     char *p;
5765     int machineWhite;
5766     char *bookHit;
5767
5768 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5769     /*
5770      * Kludge to ignore BEL characters
5771      */
5772     while (*message == '\007') message++;
5773
5774     /*
5775      * [HGM] engine debug message: ignore lines starting with '#' character
5776      */
5777     if(cps->debug && *message == '#') return;
5778
5779     /*
5780      * Look for book output
5781      */
5782     if (cps == &first && bookRequested) {
5783         if (message[0] == '\t' || message[0] == ' ') {
5784             /* Part of the book output is here; append it */
5785             strcat(bookOutput, message);
5786             strcat(bookOutput, "  \n");
5787             return;
5788         } else if (bookOutput[0] != NULLCHAR) {
5789             /* All of book output has arrived; display it */
5790             char *p = bookOutput;
5791             while (*p != NULLCHAR) {
5792                 if (*p == '\t') *p = ' ';
5793                 p++;
5794             }
5795             DisplayInformation(bookOutput);
5796             bookRequested = FALSE;
5797             /* Fall through to parse the current output */
5798         }
5799     }
5800
5801     /*
5802      * Look for machine move.
5803      */
5804     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5805         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5806     {
5807         /* This method is only useful on engines that support ping */
5808         if (cps->lastPing != cps->lastPong) {
5809           if (gameMode == BeginningOfGame) {
5810             /* Extra move from before last new; ignore */
5811             if (appData.debugMode) {
5812                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5813             }
5814           } else {
5815             if (appData.debugMode) {
5816                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5817                         cps->which, gameMode);
5818             }
5819
5820             SendToProgram("undo\n", cps);
5821           }
5822           return;
5823         }
5824
5825         switch (gameMode) {
5826           case BeginningOfGame:
5827             /* Extra move from before last reset; ignore */
5828             if (appData.debugMode) {
5829                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5830             }
5831             return;
5832
5833           case EndOfGame:
5834           case IcsIdle:
5835           default:
5836             /* Extra move after we tried to stop.  The mode test is
5837                not a reliable way of detecting this problem, but it's
5838                the best we can do on engines that don't support ping.
5839             */
5840             if (appData.debugMode) {
5841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5842                         cps->which, gameMode);
5843             }
5844             SendToProgram("undo\n", cps);
5845             return;
5846
5847           case MachinePlaysWhite:
5848           case IcsPlayingWhite:
5849             machineWhite = TRUE;
5850             break;
5851
5852           case MachinePlaysBlack:
5853           case IcsPlayingBlack:
5854             machineWhite = FALSE;
5855             break;
5856
5857           case TwoMachinesPlay:
5858             machineWhite = (cps->twoMachinesColor[0] == 'w');
5859             break;
5860         }
5861         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5862             if (appData.debugMode) {
5863                 fprintf(debugFP,
5864                         "Ignoring move out of turn by %s, gameMode %d"
5865                         ", forwardMost %d\n",
5866                         cps->which, gameMode, forwardMostMove);
5867             }
5868             return;
5869         }
5870
5871     if (appData.debugMode) { int f = forwardMostMove;
5872         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5873                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5874     }
5875         if(cps->alphaRank) AlphaRank(machineMove, 4);
5876         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5877                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5878             /* Machine move could not be parsed; ignore it. */
5879             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5880                     machineMove, cps->which);
5881             DisplayError(buf1, 0);
5882             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5883                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5884             if (gameMode == TwoMachinesPlay) {
5885               GameEnds(machineWhite ? BlackWins : WhiteWins,
5886                        buf1, GE_XBOARD);
5887             }
5888             return;
5889         }
5890
5891         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5892         /* So we have to redo legality test with true e.p. status here,  */
5893         /* to make sure an illegal e.p. capture does not slip through,   */
5894         /* to cause a forfeit on a justified illegal-move complaint      */
5895         /* of the opponent.                                              */
5896         if( gameMode==TwoMachinesPlay && appData.testLegality
5897             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5898                                                               ) {
5899            ChessMove moveType;
5900            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5901                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5902                              fromY, fromX, toY, toX, promoChar);
5903             if (appData.debugMode) {
5904                 int i;
5905                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5906                     castlingRights[forwardMostMove][i], castlingRank[i]);
5907                 fprintf(debugFP, "castling rights\n");
5908             }
5909             if(moveType == IllegalMove) {
5910                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5911                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5912                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5913                            buf1, GE_XBOARD);
5914                 return;
5915            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5916            /* [HGM] Kludge to handle engines that send FRC-style castling
5917               when they shouldn't (like TSCP-Gothic) */
5918            switch(moveType) {
5919              case WhiteASideCastleFR:
5920              case BlackASideCastleFR:
5921                toX+=2;
5922                currentMoveString[2]++;
5923                break;
5924              case WhiteHSideCastleFR:
5925              case BlackHSideCastleFR:
5926                toX--;
5927                currentMoveString[2]--;
5928                break;
5929              default: ; // nothing to do, but suppresses warning of pedantic compilers
5930            }
5931         }
5932         hintRequested = FALSE;
5933         lastHint[0] = NULLCHAR;
5934         bookRequested = FALSE;
5935         /* Program may be pondering now */
5936         cps->maybeThinking = TRUE;
5937         if (cps->sendTime == 2) cps->sendTime = 1;
5938         if (cps->offeredDraw) cps->offeredDraw--;
5939
5940 #if ZIPPY
5941         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5942             first.initDone) {
5943           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5944           ics_user_moved = 1;
5945           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5946                 char buf[3*MSG_SIZ];
5947
5948                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5949                         programStats.score / 100.,
5950                         programStats.depth,
5951                         programStats.time / 100.,
5952                         (unsigned int)programStats.nodes,
5953                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5954                         programStats.movelist);
5955                 SendToICS(buf);
5956 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5957           }
5958         }
5959 #endif
5960         /* currentMoveString is set as a side-effect of ParseOneMove */
5961         strcpy(machineMove, currentMoveString);
5962         strcat(machineMove, "\n");
5963         strcpy(moveList[forwardMostMove], machineMove);
5964
5965         /* [AS] Save move info and clear stats for next move */
5966         pvInfoList[ forwardMostMove ].score = programStats.score;
5967         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5968         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5969         ClearProgramStats();
5970         thinkOutput[0] = NULLCHAR;
5971         hiddenThinkOutputState = 0;
5972
5973         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5974
5975         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5976         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5977             int count = 0;
5978
5979             while( count < adjudicateLossPlies ) {
5980                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5981
5982                 if( count & 1 ) {
5983                     score = -score; /* Flip score for winning side */
5984                 }
5985
5986                 if( score > adjudicateLossThreshold ) {
5987                     break;
5988                 }
5989
5990                 count++;
5991             }
5992
5993             if( count >= adjudicateLossPlies ) {
5994                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5995
5996                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5997                     "Xboard adjudication", 
5998                     GE_XBOARD );
5999
6000                 return;
6001             }
6002         }
6003
6004         if( gameMode == TwoMachinesPlay ) {
6005           // [HGM] some adjudications useful with buggy engines
6006             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6007           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6008
6009
6010             if( appData.testLegality )
6011             {   /* [HGM] Some more adjudications for obstinate engines */
6012                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6013                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6014                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6015                 static int moveCount = 6;
6016                 ChessMove result;
6017                 char *reason = NULL;
6018
6019                 /* Count what is on board. */
6020                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6021                 {   ChessSquare p = boards[forwardMostMove][i][j];
6022                     int m=i;
6023
6024                     switch((int) p)
6025                     {   /* count B,N,R and other of each side */
6026                         case WhiteKing:
6027                         case BlackKing:
6028                              NrK++; break; // [HGM] atomic: count Kings
6029                         case WhiteKnight:
6030                              NrWN++; break;
6031                         case WhiteBishop:
6032                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6033                              bishopsColor |= 1 << ((i^j)&1);
6034                              NrWB++; break;
6035                         case BlackKnight:
6036                              NrBN++; break;
6037                         case BlackBishop:
6038                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6039                              bishopsColor |= 1 << ((i^j)&1);
6040                              NrBB++; break;
6041                         case WhiteRook:
6042                              NrWR++; break;
6043                         case BlackRook:
6044                              NrBR++; break;
6045                         case WhiteQueen:
6046                              NrWQ++; break;
6047                         case BlackQueen:
6048                              NrBQ++; break;
6049                         case EmptySquare: 
6050                              break;
6051                         case BlackPawn:
6052                              m = 7-i;
6053                         case WhitePawn:
6054                              PawnAdvance += m; NrPawns++;
6055                     }
6056                     NrPieces += (p != EmptySquare);
6057                     NrW += ((int)p < (int)BlackPawn);
6058                     if(gameInfo.variant == VariantXiangqi && 
6059                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6060                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6061                         NrW -= ((int)p < (int)BlackPawn);
6062                     }
6063                 }
6064
6065                 /* Some material-based adjudications that have to be made before stalemate test */
6066                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6067                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6068                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6069                      if(appData.checkMates) {
6070                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6071                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6072                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6073                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6074                          return;
6075                      }
6076                 }
6077
6078                 /* Bare King in Shatranj (loses) or Losers (wins) */
6079                 if( NrW == 1 || NrPieces - NrW == 1) {
6080                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6081                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6082                      if(appData.checkMates) {
6083                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6084                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6085                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6086                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6087                          return;
6088                      }
6089                   } else
6090                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6091                   {    /* bare King */
6092                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6093                         if(appData.checkMates) {
6094                             /* but only adjudicate if adjudication enabled */
6095                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6096                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6097                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6098                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6099                             return;
6100                         }
6101                   }
6102                 } else bare = 1;
6103
6104
6105             // don't wait for engine to announce game end if we can judge ourselves
6106             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6107                                        castlingRights[forwardMostMove]) ) {
6108               case MT_CHECK:
6109                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6110                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6111                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6112                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6113                             checkCnt++;
6114                         if(checkCnt >= 2) {
6115                             reason = "Xboard adjudication: 3rd check";
6116                             epStatus[forwardMostMove] = EP_CHECKMATE;
6117                             break;
6118                         }
6119                     }
6120                 }
6121               case MT_NONE:
6122               default:
6123                 break;
6124               case MT_STALEMATE:
6125               case MT_STAINMATE:
6126                 reason = "Xboard adjudication: Stalemate";
6127                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6128                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6129                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6130                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6131                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6132                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6133                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6134                                                                         EP_CHECKMATE : EP_WINS);
6135                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6136                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6137                 }
6138                 break;
6139               case MT_CHECKMATE:
6140                 reason = "Xboard adjudication: Checkmate";
6141                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6142                 break;
6143             }
6144
6145                 switch(i = epStatus[forwardMostMove]) {
6146                     case EP_STALEMATE:
6147                         result = GameIsDrawn; break;
6148                     case EP_CHECKMATE:
6149                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6150                     case EP_WINS:
6151                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6152                     default:
6153                         result = (ChessMove) 0;
6154                 }
6155                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6156                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6157                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6158                     GameEnds( result, reason, GE_XBOARD );
6159                     return;
6160                 }
6161
6162                 /* Next absolutely insufficient mating material. */
6163                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6164                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6165                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6166                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6167                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6168
6169                      /* always flag draws, for judging claims */
6170                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6171
6172                      if(appData.materialDraws) {
6173                          /* but only adjudicate them if adjudication enabled */
6174                          SendToProgram("force\n", cps->other); // suppress reply
6175                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6176                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6177                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6178                          return;
6179                      }
6180                 }
6181
6182                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6183                 if(NrPieces == 4 && 
6184                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6185                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6186                    || NrWN==2 || NrBN==2     /* KNNK */
6187                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6188                   ) ) {
6189                      if(--moveCount < 0 && appData.trivialDraws)
6190                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6191                           SendToProgram("force\n", cps->other); // suppress reply
6192                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6193                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6194                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6195                           return;
6196                      }
6197                 } else moveCount = 6;
6198             }
6199           }
6200           
6201           if (appData.debugMode) { int i;
6202             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6203                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6204                     appData.drawRepeats);
6205             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6206               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6207             
6208           }
6209
6210                 /* Check for rep-draws */
6211                 count = 0;
6212                 for(k = forwardMostMove-2;
6213                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6214                         epStatus[k] < EP_UNKNOWN &&
6215                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6216                     k-=2)
6217                 {   int rights=0;
6218                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6219                         /* compare castling rights */
6220                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6221                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6222                                 rights++; /* King lost rights, while rook still had them */
6223                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6224                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6225                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6226                                    rights++; /* but at least one rook lost them */
6227                         }
6228                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6229                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6230                                 rights++; 
6231                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6232                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6233                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6234                                    rights++;
6235                         }
6236                         if( rights == 0 && ++count > appData.drawRepeats-2
6237                             && appData.drawRepeats > 1) {
6238                              /* adjudicate after user-specified nr of repeats */
6239                              SendToProgram("force\n", cps->other); // suppress reply
6240                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6241                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6242                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6243                                 // [HGM] xiangqi: check for forbidden perpetuals
6244                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6245                                 for(m=forwardMostMove; m>k; m-=2) {
6246                                     if(MateTest(boards[m], PosFlags(m), 
6247                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6248                                         ourPerpetual = 0; // the current mover did not always check
6249                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6250                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6251                                         hisPerpetual = 0; // the opponent did not always check
6252                                 }
6253                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6254                                                                         ourPerpetual, hisPerpetual);
6255                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6256                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6257                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6258                                     return;
6259                                 }
6260                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6261                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6262                                 // Now check for perpetual chases
6263                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6264                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6265                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6266                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6267                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6268                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6269                                         return;
6270                                     }
6271                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6272                                         break; // Abort repetition-checking loop.
6273                                 }
6274                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6275                              }
6276                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6277                              return;
6278                         }
6279                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6280                              epStatus[forwardMostMove] = EP_REP_DRAW;
6281                     }
6282                 }
6283
6284                 /* Now we test for 50-move draws. Determine ply count */
6285                 count = forwardMostMove;
6286                 /* look for last irreversble move */
6287                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6288                     count--;
6289                 /* if we hit starting position, add initial plies */
6290                 if( count == backwardMostMove )
6291                     count -= initialRulePlies;
6292                 count = forwardMostMove - count; 
6293                 if( count >= 100)
6294                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6295                          /* this is used to judge if draw claims are legal */
6296                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6297                          SendToProgram("force\n", cps->other); // suppress reply
6298                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6299                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6300                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6301                          return;
6302                 }
6303
6304                 /* if draw offer is pending, treat it as a draw claim
6305                  * when draw condition present, to allow engines a way to
6306                  * claim draws before making their move to avoid a race
6307                  * condition occurring after their move
6308                  */
6309                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6310                          char *p = NULL;
6311                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6312                              p = "Draw claim: 50-move rule";
6313                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6314                              p = "Draw claim: 3-fold repetition";
6315                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6316                              p = "Draw claim: insufficient mating material";
6317                          if( p != NULL ) {
6318                              SendToProgram("force\n", cps->other); // suppress reply
6319                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6320                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6321                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6322                              return;
6323                          }
6324                 }
6325
6326
6327                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6328                     SendToProgram("force\n", cps->other); // suppress reply
6329                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6330                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6331
6332                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6333
6334                     return;
6335                 }
6336         }
6337
6338         bookHit = NULL;
6339         if (gameMode == TwoMachinesPlay) {
6340             /* [HGM] relaying draw offers moved to after reception of move */
6341             /* and interpreting offer as claim if it brings draw condition */
6342             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6343                 SendToProgram("draw\n", cps->other);
6344             }
6345             if (cps->other->sendTime) {
6346                 SendTimeRemaining(cps->other,
6347                                   cps->other->twoMachinesColor[0] == 'w');
6348             }
6349             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6350             if (firstMove && !bookHit) {
6351                 firstMove = FALSE;
6352                 if (cps->other->useColors) {
6353                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6354                 }
6355                 SendToProgram("go\n", cps->other);
6356             }
6357             cps->other->maybeThinking = TRUE;
6358         }
6359
6360         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6361         
6362         if (!pausing && appData.ringBellAfterMoves) {
6363             RingBell();
6364         }
6365
6366         /* 
6367          * Reenable menu items that were disabled while
6368          * machine was thinking
6369          */
6370         if (gameMode != TwoMachinesPlay)
6371             SetUserThinkingEnables();
6372
6373         // [HGM] book: after book hit opponent has received move and is now in force mode
6374         // force the book reply into it, and then fake that it outputted this move by jumping
6375         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6376         if(bookHit) {
6377                 static char bookMove[MSG_SIZ]; // a bit generous?
6378
6379                 strcpy(bookMove, "move ");
6380                 strcat(bookMove, bookHit);
6381                 message = bookMove;
6382                 cps = cps->other;
6383                 programStats.nodes = programStats.depth = programStats.time = 
6384                 programStats.score = programStats.got_only_move = 0;
6385                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6386
6387                 if(cps->lastPing != cps->lastPong) {
6388                     savedMessage = message; // args for deferred call
6389                     savedState = cps;
6390                     ScheduleDelayedEvent(DeferredBookMove, 10);
6391                     return;
6392                 }
6393                 goto FakeBookMove;
6394         }
6395
6396         return;
6397     }
6398
6399     /* Set special modes for chess engines.  Later something general
6400      *  could be added here; for now there is just one kludge feature,
6401      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6402      *  when "xboard" is given as an interactive command.
6403      */
6404     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6405         cps->useSigint = FALSE;
6406         cps->useSigterm = FALSE;
6407     }
6408     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6409       ParseFeatures(message+8, cps);
6410       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6411     }
6412
6413     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6414      * want this, I was asked to put it in, and obliged.
6415      */
6416     if (!strncmp(message, "setboard ", 9)) {
6417         Board initial_position; int i;
6418
6419         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6420
6421         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6422             DisplayError(_("Bad FEN received from engine"), 0);
6423             return ;
6424         } else {
6425            Reset(TRUE, FALSE);
6426            CopyBoard(boards[0], initial_position);
6427            initialRulePlies = FENrulePlies;
6428            epStatus[0] = FENepStatus;
6429            for( i=0; i<nrCastlingRights; i++ )
6430                 castlingRights[0][i] = FENcastlingRights[i];
6431            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6432            else gameMode = MachinePlaysBlack;                 
6433            DrawPosition(FALSE, boards[currentMove]);
6434         }
6435         return;
6436     }
6437
6438     /*
6439      * Look for communication commands
6440      */
6441     if (!strncmp(message, "telluser ", 9)) {
6442         DisplayNote(message + 9);
6443         return;
6444     }
6445     if (!strncmp(message, "tellusererror ", 14)) {
6446         DisplayError(message + 14, 0);
6447         return;
6448     }
6449     if (!strncmp(message, "tellopponent ", 13)) {
6450       if (appData.icsActive) {
6451         if (loggedOn) {
6452           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6453           SendToICS(buf1);
6454         }
6455       } else {
6456         DisplayNote(message + 13);
6457       }
6458       return;
6459     }
6460     if (!strncmp(message, "tellothers ", 11)) {
6461       if (appData.icsActive) {
6462         if (loggedOn) {
6463           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6464           SendToICS(buf1);
6465         }
6466       }
6467       return;
6468     }
6469     if (!strncmp(message, "tellall ", 8)) {
6470       if (appData.icsActive) {
6471         if (loggedOn) {
6472           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6473           SendToICS(buf1);
6474         }
6475       } else {
6476         DisplayNote(message + 8);
6477       }
6478       return;
6479     }
6480     if (strncmp(message, "warning", 7) == 0) {
6481         /* Undocumented feature, use tellusererror in new code */
6482         DisplayError(message, 0);
6483         return;
6484     }
6485     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6486         strcpy(realname, cps->tidy);
6487         strcat(realname, " query");
6488         AskQuestion(realname, buf2, buf1, cps->pr);
6489         return;
6490     }
6491     /* Commands from the engine directly to ICS.  We don't allow these to be 
6492      *  sent until we are logged on. Crafty kibitzes have been known to 
6493      *  interfere with the login process.
6494      */
6495     if (loggedOn) {
6496         if (!strncmp(message, "tellics ", 8)) {
6497             SendToICS(message + 8);
6498             SendToICS("\n");
6499             return;
6500         }
6501         if (!strncmp(message, "tellicsnoalias ", 15)) {
6502             SendToICS(ics_prefix);
6503             SendToICS(message + 15);
6504             SendToICS("\n");
6505             return;
6506         }
6507         /* The following are for backward compatibility only */
6508         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6509             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6510             SendToICS(ics_prefix);
6511             SendToICS(message);
6512             SendToICS("\n");
6513             return;
6514         }
6515     }
6516     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6517         return;
6518     }
6519     /*
6520      * If the move is illegal, cancel it and redraw the board.
6521      * Also deal with other error cases.  Matching is rather loose
6522      * here to accommodate engines written before the spec.
6523      */
6524     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6525         strncmp(message, "Error", 5) == 0) {
6526         if (StrStr(message, "name") || 
6527             StrStr(message, "rating") || StrStr(message, "?") ||
6528             StrStr(message, "result") || StrStr(message, "board") ||
6529             StrStr(message, "bk") || StrStr(message, "computer") ||
6530             StrStr(message, "variant") || StrStr(message, "hint") ||
6531             StrStr(message, "random") || StrStr(message, "depth") ||
6532             StrStr(message, "accepted")) {
6533             return;
6534         }
6535         if (StrStr(message, "protover")) {
6536           /* Program is responding to input, so it's apparently done
6537              initializing, and this error message indicates it is
6538              protocol version 1.  So we don't need to wait any longer
6539              for it to initialize and send feature commands. */
6540           FeatureDone(cps, 1);
6541           cps->protocolVersion = 1;
6542           return;
6543         }
6544         cps->maybeThinking = FALSE;
6545
6546         if (StrStr(message, "draw")) {
6547             /* Program doesn't have "draw" command */
6548             cps->sendDrawOffers = 0;
6549             return;
6550         }
6551         if (cps->sendTime != 1 &&
6552             (StrStr(message, "time") || StrStr(message, "otim"))) {
6553           /* Program apparently doesn't have "time" or "otim" command */
6554           cps->sendTime = 0;
6555           return;
6556         }
6557         if (StrStr(message, "analyze")) {
6558             cps->analysisSupport = FALSE;
6559             cps->analyzing = FALSE;
6560             Reset(FALSE, TRUE);
6561             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6562             DisplayError(buf2, 0);
6563             return;
6564         }
6565         if (StrStr(message, "(no matching move)st")) {
6566           /* Special kludge for GNU Chess 4 only */
6567           cps->stKludge = TRUE;
6568           SendTimeControl(cps, movesPerSession, timeControl,
6569                           timeIncrement, appData.searchDepth,
6570                           searchTime);
6571           return;
6572         }
6573         if (StrStr(message, "(no matching move)sd")) {
6574           /* Special kludge for GNU Chess 4 only */
6575           cps->sdKludge = TRUE;
6576           SendTimeControl(cps, movesPerSession, timeControl,
6577                           timeIncrement, appData.searchDepth,
6578                           searchTime);
6579           return;
6580         }
6581         if (!StrStr(message, "llegal")) {
6582             return;
6583         }
6584         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6585             gameMode == IcsIdle) return;
6586         if (forwardMostMove <= backwardMostMove) return;
6587         if (pausing) PauseEvent();
6588       if(appData.forceIllegal) {
6589             // [HGM] illegal: machine refused move; force position after move into it
6590           SendToProgram("force\n", cps);
6591           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6592                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6593                 // when black is to move, while there might be nothing on a2 or black
6594                 // might already have the move. So send the board as if white has the move.
6595                 // But first we must change the stm of the engine, as it refused the last move
6596                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6597                 if(WhiteOnMove(forwardMostMove)) {
6598                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6599                     SendBoard(cps, forwardMostMove); // kludgeless board
6600                 } else {
6601                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6602                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6603                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6604                 }
6605           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6606             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6607                  gameMode == TwoMachinesPlay)
6608               SendToProgram("go\n", cps);
6609             return;
6610       } else
6611         if (gameMode == PlayFromGameFile) {
6612             /* Stop reading this game file */
6613             gameMode = EditGame;
6614             ModeHighlight();
6615         }
6616         currentMove = --forwardMostMove;
6617         DisplayMove(currentMove-1); /* before DisplayMoveError */
6618         SwitchClocks();
6619         DisplayBothClocks();
6620         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6621                 parseList[currentMove], cps->which);
6622         DisplayMoveError(buf1);
6623         DrawPosition(FALSE, boards[currentMove]);
6624
6625         /* [HGM] illegal-move claim should forfeit game when Xboard */
6626         /* only passes fully legal moves                            */
6627         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6628             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6629                                 "False illegal-move claim", GE_XBOARD );
6630         }
6631         return;
6632     }
6633     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6634         /* Program has a broken "time" command that
6635            outputs a string not ending in newline.
6636            Don't use it. */
6637         cps->sendTime = 0;
6638     }
6639     
6640     /*
6641      * If chess program startup fails, exit with an error message.
6642      * Attempts to recover here are futile.
6643      */
6644     if ((StrStr(message, "unknown host") != NULL)
6645         || (StrStr(message, "No remote directory") != NULL)
6646         || (StrStr(message, "not found") != NULL)
6647         || (StrStr(message, "No such file") != NULL)
6648         || (StrStr(message, "can't alloc") != NULL)
6649         || (StrStr(message, "Permission denied") != NULL)) {
6650
6651         cps->maybeThinking = FALSE;
6652         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6653                 cps->which, cps->program, cps->host, message);
6654         RemoveInputSource(cps->isr);
6655         DisplayFatalError(buf1, 0, 1);
6656         return;
6657     }
6658     
6659     /* 
6660      * Look for hint output
6661      */
6662     if (sscanf(message, "Hint: %s", buf1) == 1) {
6663         if (cps == &first && hintRequested) {
6664             hintRequested = FALSE;
6665             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6666                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6667                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6668                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6669                                     fromY, fromX, toY, toX, promoChar, buf1);
6670                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6671                 DisplayInformation(buf2);
6672             } else {
6673                 /* Hint move could not be parsed!? */
6674               snprintf(buf2, sizeof(buf2),
6675                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6676                         buf1, cps->which);
6677                 DisplayError(buf2, 0);
6678             }
6679         } else {
6680             strcpy(lastHint, buf1);
6681         }
6682         return;
6683     }
6684
6685     /*
6686      * Ignore other messages if game is not in progress
6687      */
6688     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6689         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6690
6691     /*
6692      * look for win, lose, draw, or draw offer
6693      */
6694     if (strncmp(message, "1-0", 3) == 0) {
6695         char *p, *q, *r = "";
6696         p = strchr(message, '{');
6697         if (p) {
6698             q = strchr(p, '}');
6699             if (q) {
6700                 *q = NULLCHAR;
6701                 r = p + 1;
6702             }
6703         }
6704         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6705         return;
6706     } else if (strncmp(message, "0-1", 3) == 0) {
6707         char *p, *q, *r = "";
6708         p = strchr(message, '{');
6709         if (p) {
6710             q = strchr(p, '}');
6711             if (q) {
6712                 *q = NULLCHAR;
6713                 r = p + 1;
6714             }
6715         }
6716         /* Kludge for Arasan 4.1 bug */
6717         if (strcmp(r, "Black resigns") == 0) {
6718             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6719             return;
6720         }
6721         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6722         return;
6723     } else if (strncmp(message, "1/2", 3) == 0) {
6724         char *p, *q, *r = "";
6725         p = strchr(message, '{');
6726         if (p) {
6727             q = strchr(p, '}');
6728             if (q) {
6729                 *q = NULLCHAR;
6730                 r = p + 1;
6731             }
6732         }
6733             
6734         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6735         return;
6736
6737     } else if (strncmp(message, "White resign", 12) == 0) {
6738         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6739         return;
6740     } else if (strncmp(message, "Black resign", 12) == 0) {
6741         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6742         return;
6743     } else if (strncmp(message, "White matches", 13) == 0 ||
6744                strncmp(message, "Black matches", 13) == 0   ) {
6745         /* [HGM] ignore GNUShogi noises */
6746         return;
6747     } else if (strncmp(message, "White", 5) == 0 &&
6748                message[5] != '(' &&
6749                StrStr(message, "Black") == NULL) {
6750         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6751         return;
6752     } else if (strncmp(message, "Black", 5) == 0 &&
6753                message[5] != '(') {
6754         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6755         return;
6756     } else if (strcmp(message, "resign") == 0 ||
6757                strcmp(message, "computer resigns") == 0) {
6758         switch (gameMode) {
6759           case MachinePlaysBlack:
6760           case IcsPlayingBlack:
6761             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6762             break;
6763           case MachinePlaysWhite:
6764           case IcsPlayingWhite:
6765             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6766             break;
6767           case TwoMachinesPlay:
6768             if (cps->twoMachinesColor[0] == 'w')
6769               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6770             else
6771               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6772             break;
6773           default:
6774             /* can't happen */
6775             break;
6776         }
6777         return;
6778     } else if (strncmp(message, "opponent mates", 14) == 0) {
6779         switch (gameMode) {
6780           case MachinePlaysBlack:
6781           case IcsPlayingBlack:
6782             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6783             break;
6784           case MachinePlaysWhite:
6785           case IcsPlayingWhite:
6786             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6787             break;
6788           case TwoMachinesPlay:
6789             if (cps->twoMachinesColor[0] == 'w')
6790               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6791             else
6792               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6793             break;
6794           default:
6795             /* can't happen */
6796             break;
6797         }
6798         return;
6799     } else if (strncmp(message, "computer mates", 14) == 0) {
6800         switch (gameMode) {
6801           case MachinePlaysBlack:
6802           case IcsPlayingBlack:
6803             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6804             break;
6805           case MachinePlaysWhite:
6806           case IcsPlayingWhite:
6807             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6808             break;
6809           case TwoMachinesPlay:
6810             if (cps->twoMachinesColor[0] == 'w')
6811               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6812             else
6813               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6814             break;
6815           default:
6816             /* can't happen */
6817             break;
6818         }
6819         return;
6820     } else if (strncmp(message, "checkmate", 9) == 0) {
6821         if (WhiteOnMove(forwardMostMove)) {
6822             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6823         } else {
6824             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6825         }
6826         return;
6827     } else if (strstr(message, "Draw") != NULL ||
6828                strstr(message, "game is a draw") != NULL) {
6829         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6830         return;
6831     } else if (strstr(message, "offer") != NULL &&
6832                strstr(message, "draw") != NULL) {
6833 #if ZIPPY
6834         if (appData.zippyPlay && first.initDone) {
6835             /* Relay offer to ICS */
6836             SendToICS(ics_prefix);
6837             SendToICS("draw\n");
6838         }
6839 #endif
6840         cps->offeredDraw = 2; /* valid until this engine moves twice */
6841         if (gameMode == TwoMachinesPlay) {
6842             if (cps->other->offeredDraw) {
6843                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6844             /* [HGM] in two-machine mode we delay relaying draw offer      */
6845             /* until after we also have move, to see if it is really claim */
6846             }
6847         } else if (gameMode == MachinePlaysWhite ||
6848                    gameMode == MachinePlaysBlack) {
6849           if (userOfferedDraw) {
6850             DisplayInformation(_("Machine accepts your draw offer"));
6851             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6852           } else {
6853             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6854           }
6855         }
6856     }
6857
6858     
6859     /*
6860      * Look for thinking output
6861      */
6862     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6863           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6864                                 ) {
6865         int plylev, mvleft, mvtot, curscore, time;
6866         char mvname[MOVE_LEN];
6867         u64 nodes; // [DM]
6868         char plyext;
6869         int ignore = FALSE;
6870         int prefixHint = FALSE;
6871         mvname[0] = NULLCHAR;
6872
6873         switch (gameMode) {
6874           case MachinePlaysBlack:
6875           case IcsPlayingBlack:
6876             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6877             break;
6878           case MachinePlaysWhite:
6879           case IcsPlayingWhite:
6880             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6881             break;
6882           case AnalyzeMode:
6883           case AnalyzeFile:
6884             break;
6885           case IcsObserving: /* [DM] icsEngineAnalyze */
6886             if (!appData.icsEngineAnalyze) ignore = TRUE;
6887             break;
6888           case TwoMachinesPlay:
6889             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6890                 ignore = TRUE;
6891             }
6892             break;
6893           default:
6894             ignore = TRUE;
6895             break;
6896         }
6897
6898         if (!ignore) {
6899             buf1[0] = NULLCHAR;
6900             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6901                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6902
6903                 if (plyext != ' ' && plyext != '\t') {
6904                     time *= 100;
6905                 }
6906
6907                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6908                 if( cps->scoreIsAbsolute && 
6909                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6910                 {
6911                     curscore = -curscore;
6912                 }
6913
6914
6915                 programStats.depth = plylev;
6916                 programStats.nodes = nodes;
6917                 programStats.time = time;
6918                 programStats.score = curscore;
6919                 programStats.got_only_move = 0;
6920
6921                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6922                         int ticklen;
6923
6924                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6925                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6926                         if(WhiteOnMove(forwardMostMove)) 
6927                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6928                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6929                 }
6930
6931                 /* Buffer overflow protection */
6932                 if (buf1[0] != NULLCHAR) {
6933                     if (strlen(buf1) >= sizeof(programStats.movelist)
6934                         && appData.debugMode) {
6935                         fprintf(debugFP,
6936                                 "PV is too long; using the first %d bytes.\n",
6937                                 sizeof(programStats.movelist) - 1);
6938                     }
6939
6940                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6941                 } else {
6942                     sprintf(programStats.movelist, " no PV\n");
6943                 }
6944
6945                 if (programStats.seen_stat) {
6946                     programStats.ok_to_send = 1;
6947                 }
6948
6949                 if (strchr(programStats.movelist, '(') != NULL) {
6950                     programStats.line_is_book = 1;
6951                     programStats.nr_moves = 0;
6952                     programStats.moves_left = 0;
6953                 } else {
6954                     programStats.line_is_book = 0;
6955                 }
6956
6957                 SendProgramStatsToFrontend( cps, &programStats );
6958
6959                 /* 
6960                     [AS] Protect the thinkOutput buffer from overflow... this
6961                     is only useful if buf1 hasn't overflowed first!
6962                 */
6963                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6964                         plylev, 
6965                         (gameMode == TwoMachinesPlay ?
6966                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6967                         ((double) curscore) / 100.0,
6968                         prefixHint ? lastHint : "",
6969                         prefixHint ? " " : "" );
6970
6971                 if( buf1[0] != NULLCHAR ) {
6972                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6973
6974                     if( strlen(buf1) > max_len ) {
6975                         if( appData.debugMode) {
6976                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6977                         }
6978                         buf1[max_len+1] = '\0';
6979                     }
6980
6981                     strcat( thinkOutput, buf1 );
6982                 }
6983
6984                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6985                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6986                     DisplayMove(currentMove - 1);
6987                 }
6988                 return;
6989
6990             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6991                 /* crafty (9.25+) says "(only move) <move>"
6992                  * if there is only 1 legal move
6993                  */
6994                 sscanf(p, "(only move) %s", buf1);
6995                 sprintf(thinkOutput, "%s (only move)", buf1);
6996                 sprintf(programStats.movelist, "%s (only move)", buf1);
6997                 programStats.depth = 1;
6998                 programStats.nr_moves = 1;
6999                 programStats.moves_left = 1;
7000                 programStats.nodes = 1;
7001                 programStats.time = 1;
7002                 programStats.got_only_move = 1;
7003
7004                 /* Not really, but we also use this member to
7005                    mean "line isn't going to change" (Crafty
7006                    isn't searching, so stats won't change) */
7007                 programStats.line_is_book = 1;
7008
7009                 SendProgramStatsToFrontend( cps, &programStats );
7010                 
7011                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7012                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7013                     DisplayMove(currentMove - 1);
7014                 }
7015                 return;
7016             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7017                               &time, &nodes, &plylev, &mvleft,
7018                               &mvtot, mvname) >= 5) {
7019                 /* The stat01: line is from Crafty (9.29+) in response
7020                    to the "." command */
7021                 programStats.seen_stat = 1;
7022                 cps->maybeThinking = TRUE;
7023
7024                 if (programStats.got_only_move || !appData.periodicUpdates)
7025                   return;
7026
7027                 programStats.depth = plylev;
7028                 programStats.time = time;
7029                 programStats.nodes = nodes;
7030                 programStats.moves_left = mvleft;
7031                 programStats.nr_moves = mvtot;
7032                 strcpy(programStats.move_name, mvname);
7033                 programStats.ok_to_send = 1;
7034                 programStats.movelist[0] = '\0';
7035
7036                 SendProgramStatsToFrontend( cps, &programStats );
7037
7038                 return;
7039
7040             } else if (strncmp(message,"++",2) == 0) {
7041                 /* Crafty 9.29+ outputs this */
7042                 programStats.got_fail = 2;
7043                 return;
7044
7045             } else if (strncmp(message,"--",2) == 0) {
7046                 /* Crafty 9.29+ outputs this */
7047                 programStats.got_fail = 1;
7048                 return;
7049
7050             } else if (thinkOutput[0] != NULLCHAR &&
7051                        strncmp(message, "    ", 4) == 0) {
7052                 unsigned message_len;
7053
7054                 p = message;
7055                 while (*p && *p == ' ') p++;
7056
7057                 message_len = strlen( p );
7058
7059                 /* [AS] Avoid buffer overflow */
7060                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7061                     strcat(thinkOutput, " ");
7062                     strcat(thinkOutput, p);
7063                 }
7064
7065                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7066                     strcat(programStats.movelist, " ");
7067                     strcat(programStats.movelist, p);
7068                 }
7069
7070                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7071                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7072                     DisplayMove(currentMove - 1);
7073                 }
7074                 return;
7075             }
7076         }
7077         else {
7078             buf1[0] = NULLCHAR;
7079
7080             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7081                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7082             {
7083                 ChessProgramStats cpstats;
7084
7085                 if (plyext != ' ' && plyext != '\t') {
7086                     time *= 100;
7087                 }
7088
7089                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7090                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7091                     curscore = -curscore;
7092                 }
7093
7094                 cpstats.depth = plylev;
7095                 cpstats.nodes = nodes;
7096                 cpstats.time = time;
7097                 cpstats.score = curscore;
7098                 cpstats.got_only_move = 0;
7099                 cpstats.movelist[0] = '\0';
7100
7101                 if (buf1[0] != NULLCHAR) {
7102                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7103                 }
7104
7105                 cpstats.ok_to_send = 0;
7106                 cpstats.line_is_book = 0;
7107                 cpstats.nr_moves = 0;
7108                 cpstats.moves_left = 0;
7109
7110                 SendProgramStatsToFrontend( cps, &cpstats );
7111             }
7112         }
7113     }
7114 }
7115
7116
7117 /* Parse a game score from the character string "game", and
7118    record it as the history of the current game.  The game
7119    score is NOT assumed to start from the standard position. 
7120    The display is not updated in any way.
7121    */
7122 void
7123 ParseGameHistory(game)
7124      char *game;
7125 {
7126     ChessMove moveType;
7127     int fromX, fromY, toX, toY, boardIndex;
7128     char promoChar;
7129     char *p, *q;
7130     char buf[MSG_SIZ];
7131
7132     if (appData.debugMode)
7133       fprintf(debugFP, "Parsing game history: %s\n", game);
7134
7135     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7136     gameInfo.site = StrSave(appData.icsHost);
7137     gameInfo.date = PGNDate();
7138     gameInfo.round = StrSave("-");
7139
7140     /* Parse out names of players */
7141     while (*game == ' ') game++;
7142     p = buf;
7143     while (*game != ' ') *p++ = *game++;
7144     *p = NULLCHAR;
7145     gameInfo.white = StrSave(buf);
7146     while (*game == ' ') game++;
7147     p = buf;
7148     while (*game != ' ' && *game != '\n') *p++ = *game++;
7149     *p = NULLCHAR;
7150     gameInfo.black = StrSave(buf);
7151
7152     /* Parse moves */
7153     boardIndex = blackPlaysFirst ? 1 : 0;
7154     yynewstr(game);
7155     for (;;) {
7156         yyboardindex = boardIndex;
7157         moveType = (ChessMove) yylex();
7158         switch (moveType) {
7159           case IllegalMove:             /* maybe suicide chess, etc. */
7160   if (appData.debugMode) {
7161     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7162     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7163     setbuf(debugFP, NULL);
7164   }
7165           case WhitePromotionChancellor:
7166           case BlackPromotionChancellor:
7167           case WhitePromotionArchbishop:
7168           case BlackPromotionArchbishop:
7169           case WhitePromotionQueen:
7170           case BlackPromotionQueen:
7171           case WhitePromotionRook:
7172           case BlackPromotionRook:
7173           case WhitePromotionBishop:
7174           case BlackPromotionBishop:
7175           case WhitePromotionKnight:
7176           case BlackPromotionKnight:
7177           case WhitePromotionKing:
7178           case BlackPromotionKing:
7179           case NormalMove:
7180           case WhiteCapturesEnPassant:
7181           case BlackCapturesEnPassant:
7182           case WhiteKingSideCastle:
7183           case WhiteQueenSideCastle:
7184           case BlackKingSideCastle:
7185           case BlackQueenSideCastle:
7186           case WhiteKingSideCastleWild:
7187           case WhiteQueenSideCastleWild:
7188           case BlackKingSideCastleWild:
7189           case BlackQueenSideCastleWild:
7190           /* PUSH Fabien */
7191           case WhiteHSideCastleFR:
7192           case WhiteASideCastleFR:
7193           case BlackHSideCastleFR:
7194           case BlackASideCastleFR:
7195           /* POP Fabien */
7196             fromX = currentMoveString[0] - AAA;
7197             fromY = currentMoveString[1] - ONE;
7198             toX = currentMoveString[2] - AAA;
7199             toY = currentMoveString[3] - ONE;
7200             promoChar = currentMoveString[4];
7201             break;
7202           case WhiteDrop:
7203           case BlackDrop:
7204             fromX = moveType == WhiteDrop ?
7205               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7206             (int) CharToPiece(ToLower(currentMoveString[0]));
7207             fromY = DROP_RANK;
7208             toX = currentMoveString[2] - AAA;
7209             toY = currentMoveString[3] - ONE;
7210             promoChar = NULLCHAR;
7211             break;
7212           case AmbiguousMove:
7213             /* bug? */
7214             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7215   if (appData.debugMode) {
7216     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7217     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7218     setbuf(debugFP, NULL);
7219   }
7220             DisplayError(buf, 0);
7221             return;
7222           case ImpossibleMove:
7223             /* bug? */
7224             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7225   if (appData.debugMode) {
7226     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7227     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7228     setbuf(debugFP, NULL);
7229   }
7230             DisplayError(buf, 0);
7231             return;
7232           case (ChessMove) 0:   /* end of file */
7233             if (boardIndex < backwardMostMove) {
7234                 /* Oops, gap.  How did that happen? */
7235                 DisplayError(_("Gap in move list"), 0);
7236                 return;
7237             }
7238             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7239             if (boardIndex > forwardMostMove) {
7240                 forwardMostMove = boardIndex;
7241             }
7242             return;
7243           case ElapsedTime:
7244             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7245                 strcat(parseList[boardIndex-1], " ");
7246                 strcat(parseList[boardIndex-1], yy_text);
7247             }
7248             continue;
7249           case Comment:
7250           case PGNTag:
7251           case NAG:
7252           default:
7253             /* ignore */
7254             continue;
7255           case WhiteWins:
7256           case BlackWins:
7257           case GameIsDrawn:
7258           case GameUnfinished:
7259             if (gameMode == IcsExamining) {
7260                 if (boardIndex < backwardMostMove) {
7261                     /* Oops, gap.  How did that happen? */
7262                     return;
7263                 }
7264                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7265                 return;
7266             }
7267             gameInfo.result = moveType;
7268             p = strchr(yy_text, '{');
7269             if (p == NULL) p = strchr(yy_text, '(');
7270             if (p == NULL) {
7271                 p = yy_text;
7272                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7273             } else {
7274                 q = strchr(p, *p == '{' ? '}' : ')');
7275                 if (q != NULL) *q = NULLCHAR;
7276                 p++;
7277             }
7278             gameInfo.resultDetails = StrSave(p);
7279             continue;
7280         }
7281         if (boardIndex >= forwardMostMove &&
7282             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7283             backwardMostMove = blackPlaysFirst ? 1 : 0;
7284             return;
7285         }
7286         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7287                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7288                                  parseList[boardIndex]);
7289         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7290         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7291         /* currentMoveString is set as a side-effect of yylex */
7292         strcpy(moveList[boardIndex], currentMoveString);
7293         strcat(moveList[boardIndex], "\n");
7294         boardIndex++;
7295         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7296                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7297         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7298                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7299           case MT_NONE:
7300           case MT_STALEMATE:
7301           default:
7302             break;
7303           case MT_CHECK:
7304             if(gameInfo.variant != VariantShogi)
7305                 strcat(parseList[boardIndex - 1], "+");
7306             break;
7307           case MT_CHECKMATE:
7308           case MT_STAINMATE:
7309             strcat(parseList[boardIndex - 1], "#");
7310             break;
7311         }
7312     }
7313 }
7314
7315
7316 /* Apply a move to the given board  */
7317 void
7318 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7319      int fromX, fromY, toX, toY;
7320      int promoChar;
7321      Board board;
7322      char *castling;
7323      char *ep;
7324 {
7325   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7326
7327     /* [HGM] compute & store e.p. status and castling rights for new position */
7328     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7329     { int i;
7330
7331       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7332       oldEP = *ep;
7333       *ep = EP_NONE;
7334
7335       if( board[toY][toX] != EmptySquare ) 
7336            *ep = EP_CAPTURE;  
7337
7338       if( board[fromY][fromX] == WhitePawn ) {
7339            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7340                *ep = EP_PAWN_MOVE;
7341            if( toY-fromY==2) {
7342                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7343                         gameInfo.variant != VariantBerolina || toX < fromX)
7344                       *ep = toX | berolina;
7345                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7346                         gameInfo.variant != VariantBerolina || toX > fromX) 
7347                       *ep = toX;
7348            }
7349       } else 
7350       if( board[fromY][fromX] == BlackPawn ) {
7351            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7352                *ep = EP_PAWN_MOVE; 
7353            if( toY-fromY== -2) {
7354                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7355                         gameInfo.variant != VariantBerolina || toX < fromX)
7356                       *ep = toX | berolina;
7357                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7358                         gameInfo.variant != VariantBerolina || toX > fromX) 
7359                       *ep = toX;
7360            }
7361        }
7362
7363        for(i=0; i<nrCastlingRights; i++) {
7364            if(castling[i] == fromX && castlingRank[i] == fromY ||
7365               castling[i] == toX   && castlingRank[i] == toY   
7366              ) castling[i] = -1; // revoke for moved or captured piece
7367        }
7368
7369     }
7370
7371   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7372   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7373        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7374          
7375   if (fromX == toX && fromY == toY) return;
7376
7377   if (fromY == DROP_RANK) {
7378         /* must be first */
7379         piece = board[toY][toX] = (ChessSquare) fromX;
7380   } else {
7381      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7382      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7383      if(gameInfo.variant == VariantKnightmate)
7384          king += (int) WhiteUnicorn - (int) WhiteKing;
7385
7386     /* Code added by Tord: */
7387     /* FRC castling assumed when king captures friendly rook. */
7388     if (board[fromY][fromX] == WhiteKing &&
7389              board[toY][toX] == WhiteRook) {
7390       board[fromY][fromX] = EmptySquare;
7391       board[toY][toX] = EmptySquare;
7392       if(toX > fromX) {
7393         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7394       } else {
7395         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7396       }
7397     } else if (board[fromY][fromX] == BlackKing &&
7398                board[toY][toX] == BlackRook) {
7399       board[fromY][fromX] = EmptySquare;
7400       board[toY][toX] = EmptySquare;
7401       if(toX > fromX) {
7402         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7403       } else {
7404         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7405       }
7406     /* End of code added by Tord */
7407
7408     } else if (board[fromY][fromX] == king
7409         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7410         && toY == fromY && toX > fromX+1) {
7411         board[fromY][fromX] = EmptySquare;
7412         board[toY][toX] = king;
7413         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7414         board[fromY][BOARD_RGHT-1] = EmptySquare;
7415     } else if (board[fromY][fromX] == king
7416         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7417                && toY == fromY && toX < fromX-1) {
7418         board[fromY][fromX] = EmptySquare;
7419         board[toY][toX] = king;
7420         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7421         board[fromY][BOARD_LEFT] = EmptySquare;
7422     } else if (board[fromY][fromX] == WhitePawn
7423                && toY == BOARD_HEIGHT-1
7424                && gameInfo.variant != VariantXiangqi
7425                ) {
7426         /* white pawn promotion */
7427         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7428         if (board[toY][toX] == EmptySquare) {
7429             board[toY][toX] = WhiteQueen;
7430         }
7431         if(gameInfo.variant==VariantBughouse ||
7432            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7433             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7434         board[fromY][fromX] = EmptySquare;
7435     } else if ((fromY == BOARD_HEIGHT-4)
7436                && (toX != fromX)
7437                && gameInfo.variant != VariantXiangqi
7438                && gameInfo.variant != VariantBerolina
7439                && (board[fromY][fromX] == WhitePawn)
7440                && (board[toY][toX] == EmptySquare)) {
7441         board[fromY][fromX] = EmptySquare;
7442         board[toY][toX] = WhitePawn;
7443         captured = board[toY - 1][toX];
7444         board[toY - 1][toX] = EmptySquare;
7445     } else if ((fromY == BOARD_HEIGHT-4)
7446                && (toX == fromX)
7447                && gameInfo.variant == VariantBerolina
7448                && (board[fromY][fromX] == WhitePawn)
7449                && (board[toY][toX] == EmptySquare)) {
7450         board[fromY][fromX] = EmptySquare;
7451         board[toY][toX] = WhitePawn;
7452         if(oldEP & EP_BEROLIN_A) {
7453                 captured = board[fromY][fromX-1];
7454                 board[fromY][fromX-1] = EmptySquare;
7455         }else{  captured = board[fromY][fromX+1];
7456                 board[fromY][fromX+1] = EmptySquare;
7457         }
7458     } else if (board[fromY][fromX] == king
7459         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7460                && toY == fromY && toX > fromX+1) {
7461         board[fromY][fromX] = EmptySquare;
7462         board[toY][toX] = king;
7463         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7464         board[fromY][BOARD_RGHT-1] = EmptySquare;
7465     } else if (board[fromY][fromX] == king
7466         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7467                && toY == fromY && toX < fromX-1) {
7468         board[fromY][fromX] = EmptySquare;
7469         board[toY][toX] = king;
7470         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7471         board[fromY][BOARD_LEFT] = EmptySquare;
7472     } else if (fromY == 7 && fromX == 3
7473                && board[fromY][fromX] == BlackKing
7474                && toY == 7 && toX == 5) {
7475         board[fromY][fromX] = EmptySquare;
7476         board[toY][toX] = BlackKing;
7477         board[fromY][7] = EmptySquare;
7478         board[toY][4] = BlackRook;
7479     } else if (fromY == 7 && fromX == 3
7480                && board[fromY][fromX] == BlackKing
7481                && toY == 7 && toX == 1) {
7482         board[fromY][fromX] = EmptySquare;
7483         board[toY][toX] = BlackKing;
7484         board[fromY][0] = EmptySquare;
7485         board[toY][2] = BlackRook;
7486     } else if (board[fromY][fromX] == BlackPawn
7487                && toY == 0
7488                && gameInfo.variant != VariantXiangqi
7489                ) {
7490         /* black pawn promotion */
7491         board[0][toX] = CharToPiece(ToLower(promoChar));
7492         if (board[0][toX] == EmptySquare) {
7493             board[0][toX] = BlackQueen;
7494         }
7495         if(gameInfo.variant==VariantBughouse ||
7496            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7497             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7498         board[fromY][fromX] = EmptySquare;
7499     } else if ((fromY == 3)
7500                && (toX != fromX)
7501                && gameInfo.variant != VariantXiangqi
7502                && gameInfo.variant != VariantBerolina
7503                && (board[fromY][fromX] == BlackPawn)
7504                && (board[toY][toX] == EmptySquare)) {
7505         board[fromY][fromX] = EmptySquare;
7506         board[toY][toX] = BlackPawn;
7507         captured = board[toY + 1][toX];
7508         board[toY + 1][toX] = EmptySquare;
7509     } else if ((fromY == 3)
7510                && (toX == fromX)
7511                && gameInfo.variant == VariantBerolina
7512                && (board[fromY][fromX] == BlackPawn)
7513                && (board[toY][toX] == EmptySquare)) {
7514         board[fromY][fromX] = EmptySquare;
7515         board[toY][toX] = BlackPawn;
7516         if(oldEP & EP_BEROLIN_A) {
7517                 captured = board[fromY][fromX-1];
7518                 board[fromY][fromX-1] = EmptySquare;
7519         }else{  captured = board[fromY][fromX+1];
7520                 board[fromY][fromX+1] = EmptySquare;
7521         }
7522     } else {
7523         board[toY][toX] = board[fromY][fromX];
7524         board[fromY][fromX] = EmptySquare;
7525     }
7526
7527     /* [HGM] now we promote for Shogi, if needed */
7528     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7529         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7530   }
7531
7532     if (gameInfo.holdingsWidth != 0) {
7533
7534       /* !!A lot more code needs to be written to support holdings  */
7535       /* [HGM] OK, so I have written it. Holdings are stored in the */
7536       /* penultimate board files, so they are automaticlly stored   */
7537       /* in the game history.                                       */
7538       if (fromY == DROP_RANK) {
7539         /* Delete from holdings, by decreasing count */
7540         /* and erasing image if necessary            */
7541         p = (int) fromX;
7542         if(p < (int) BlackPawn) { /* white drop */
7543              p -= (int)WhitePawn;
7544                  p = PieceToNumber((ChessSquare)p);
7545              if(p >= gameInfo.holdingsSize) p = 0;
7546              if(--board[p][BOARD_WIDTH-2] <= 0)
7547                   board[p][BOARD_WIDTH-1] = EmptySquare;
7548              if((int)board[p][BOARD_WIDTH-2] < 0)
7549                         board[p][BOARD_WIDTH-2] = 0;
7550         } else {                  /* black drop */
7551              p -= (int)BlackPawn;
7552                  p = PieceToNumber((ChessSquare)p);
7553              if(p >= gameInfo.holdingsSize) p = 0;
7554              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7555                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7556              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7557                         board[BOARD_HEIGHT-1-p][1] = 0;
7558         }
7559       }
7560       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7561           && gameInfo.variant != VariantBughouse        ) {
7562         /* [HGM] holdings: Add to holdings, if holdings exist */
7563         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7564                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7565                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7566         }
7567         p = (int) captured;
7568         if (p >= (int) BlackPawn) {
7569           p -= (int)BlackPawn;
7570           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7571                   /* in Shogi restore piece to its original  first */
7572                   captured = (ChessSquare) (DEMOTED captured);
7573                   p = DEMOTED p;
7574           }
7575           p = PieceToNumber((ChessSquare)p);
7576           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7577           board[p][BOARD_WIDTH-2]++;
7578           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7579         } else {
7580           p -= (int)WhitePawn;
7581           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7582                   captured = (ChessSquare) (DEMOTED captured);
7583                   p = DEMOTED p;
7584           }
7585           p = PieceToNumber((ChessSquare)p);
7586           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7587           board[BOARD_HEIGHT-1-p][1]++;
7588           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7589         }
7590       }
7591     } else if (gameInfo.variant == VariantAtomic) {
7592       if (captured != EmptySquare) {
7593         int y, x;
7594         for (y = toY-1; y <= toY+1; y++) {
7595           for (x = toX-1; x <= toX+1; x++) {
7596             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7597                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7598               board[y][x] = EmptySquare;
7599             }
7600           }
7601         }
7602         board[toY][toX] = EmptySquare;
7603       }
7604     }
7605     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7606         /* [HGM] Shogi promotions */
7607         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7608     }
7609
7610     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7611                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7612         // [HGM] superchess: take promotion piece out of holdings
7613         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7614         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7615             if(!--board[k][BOARD_WIDTH-2])
7616                 board[k][BOARD_WIDTH-1] = EmptySquare;
7617         } else {
7618             if(!--board[BOARD_HEIGHT-1-k][1])
7619                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7620         }
7621     }
7622
7623 }
7624
7625 /* Updates forwardMostMove */
7626 void
7627 MakeMove(fromX, fromY, toX, toY, promoChar)
7628      int fromX, fromY, toX, toY;
7629      int promoChar;
7630 {
7631 //    forwardMostMove++; // [HGM] bare: moved downstream
7632
7633     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7634         int timeLeft; static int lastLoadFlag=0; int king, piece;
7635         piece = boards[forwardMostMove][fromY][fromX];
7636         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7637         if(gameInfo.variant == VariantKnightmate)
7638             king += (int) WhiteUnicorn - (int) WhiteKing;
7639         if(forwardMostMove == 0) {
7640             if(blackPlaysFirst) 
7641                 fprintf(serverMoves, "%s;", second.tidy);
7642             fprintf(serverMoves, "%s;", first.tidy);
7643             if(!blackPlaysFirst) 
7644                 fprintf(serverMoves, "%s;", second.tidy);
7645         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7646         lastLoadFlag = loadFlag;
7647         // print base move
7648         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7649         // print castling suffix
7650         if( toY == fromY && piece == king ) {
7651             if(toX-fromX > 1)
7652                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7653             if(fromX-toX >1)
7654                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7655         }
7656         // e.p. suffix
7657         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7658              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7659              boards[forwardMostMove][toY][toX] == EmptySquare
7660              && fromX != toX )
7661                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7662         // promotion suffix
7663         if(promoChar != NULLCHAR)
7664                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7665         if(!loadFlag) {
7666             fprintf(serverMoves, "/%d/%d",
7667                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7668             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7669             else                      timeLeft = blackTimeRemaining/1000;
7670             fprintf(serverMoves, "/%d", timeLeft);
7671         }
7672         fflush(serverMoves);
7673     }
7674
7675     if (forwardMostMove+1 >= MAX_MOVES) {
7676       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7677                         0, 1);
7678       return;
7679     }
7680     if (commentList[forwardMostMove+1] != NULL) {
7681         free(commentList[forwardMostMove+1]);
7682         commentList[forwardMostMove+1] = NULL;
7683     }
7684     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7685     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7686     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7687                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7688     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7689     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7690     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7691     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7692     gameInfo.result = GameUnfinished;
7693     if (gameInfo.resultDetails != NULL) {
7694         free(gameInfo.resultDetails);
7695         gameInfo.resultDetails = NULL;
7696     }
7697     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7698                               moveList[forwardMostMove - 1]);
7699     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7700                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7701                              fromY, fromX, toY, toX, promoChar,
7702                              parseList[forwardMostMove - 1]);
7703     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7704                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7705                             castlingRights[forwardMostMove]) ) {
7706       case MT_NONE:
7707       case MT_STALEMATE:
7708       default:
7709         break;
7710       case MT_CHECK:
7711         if(gameInfo.variant != VariantShogi)
7712             strcat(parseList[forwardMostMove - 1], "+");
7713         break;
7714       case MT_CHECKMATE:
7715       case MT_STAINMATE:
7716         strcat(parseList[forwardMostMove - 1], "#");
7717         break;
7718     }
7719     if (appData.debugMode) {
7720         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7721     }
7722
7723 }
7724
7725 /* Updates currentMove if not pausing */
7726 void
7727 ShowMove(fromX, fromY, toX, toY)
7728 {
7729     int instant = (gameMode == PlayFromGameFile) ?
7730         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7731     if(appData.noGUI) return;
7732     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7733         if (!instant) {
7734             if (forwardMostMove == currentMove + 1) {
7735                 AnimateMove(boards[forwardMostMove - 1],
7736                             fromX, fromY, toX, toY);
7737             }
7738             if (appData.highlightLastMove) {
7739                 SetHighlights(fromX, fromY, toX, toY);
7740             }
7741         }
7742         currentMove = forwardMostMove;
7743     }
7744
7745     if (instant) return;
7746
7747     DisplayMove(currentMove - 1);
7748     DrawPosition(FALSE, boards[currentMove]);
7749     DisplayBothClocks();
7750     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7751 }
7752
7753 void SendEgtPath(ChessProgramState *cps)
7754 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7755         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7756
7757         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7758
7759         while(*p) {
7760             char c, *q = name+1, *r, *s;
7761
7762             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7763             while(*p && *p != ',') *q++ = *p++;
7764             *q++ = ':'; *q = 0;
7765             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7766                 strcmp(name, ",nalimov:") == 0 ) {
7767                 // take nalimov path from the menu-changeable option first, if it is defined
7768                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7769                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7770             } else
7771             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7772                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7773                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7774                 s = r = StrStr(s, ":") + 1; // beginning of path info
7775                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7776                 c = *r; *r = 0;             // temporarily null-terminate path info
7777                     *--q = 0;               // strip of trailig ':' from name
7778                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7779                 *r = c;
7780                 SendToProgram(buf,cps);     // send egtbpath command for this format
7781             }
7782             if(*p == ',') p++; // read away comma to position for next format name
7783         }
7784 }
7785
7786 void
7787 InitChessProgram(cps, setup)
7788      ChessProgramState *cps;
7789      int setup; /* [HGM] needed to setup FRC opening position */
7790 {
7791     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7792     if (appData.noChessProgram) return;
7793     hintRequested = FALSE;
7794     bookRequested = FALSE;
7795
7796     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7797     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7798     if(cps->memSize) { /* [HGM] memory */
7799         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7800         SendToProgram(buf, cps);
7801     }
7802     SendEgtPath(cps); /* [HGM] EGT */
7803     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7804         sprintf(buf, "cores %d\n", appData.smpCores);
7805         SendToProgram(buf, cps);
7806     }
7807
7808     SendToProgram(cps->initString, cps);
7809     if (gameInfo.variant != VariantNormal &&
7810         gameInfo.variant != VariantLoadable
7811         /* [HGM] also send variant if board size non-standard */
7812         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7813                                             ) {
7814       char *v = VariantName(gameInfo.variant);
7815       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7816         /* [HGM] in protocol 1 we have to assume all variants valid */
7817         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7818         DisplayFatalError(buf, 0, 1);
7819         return;
7820       }
7821
7822       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7823       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7824       if( gameInfo.variant == VariantXiangqi )
7825            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7826       if( gameInfo.variant == VariantShogi )
7827            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7828       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7829            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7830       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7831                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7832            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7833       if( gameInfo.variant == VariantCourier )
7834            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7835       if( gameInfo.variant == VariantSuper )
7836            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7837       if( gameInfo.variant == VariantGreat )
7838            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7839
7840       if(overruled) {
7841            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7842                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7843            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7844            if(StrStr(cps->variants, b) == NULL) { 
7845                // specific sized variant not known, check if general sizing allowed
7846                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7847                    if(StrStr(cps->variants, "boardsize") == NULL) {
7848                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7849                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7850                        DisplayFatalError(buf, 0, 1);
7851                        return;
7852                    }
7853                    /* [HGM] here we really should compare with the maximum supported board size */
7854                }
7855            }
7856       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7857       sprintf(buf, "variant %s\n", b);
7858       SendToProgram(buf, cps);
7859     }
7860     currentlyInitializedVariant = gameInfo.variant;
7861
7862     /* [HGM] send opening position in FRC to first engine */
7863     if(setup) {
7864           SendToProgram("force\n", cps);
7865           SendBoard(cps, 0);
7866           /* engine is now in force mode! Set flag to wake it up after first move. */
7867           setboardSpoiledMachineBlack = 1;
7868     }
7869
7870     if (cps->sendICS) {
7871       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7872       SendToProgram(buf, cps);
7873     }
7874     cps->maybeThinking = FALSE;
7875     cps->offeredDraw = 0;
7876     if (!appData.icsActive) {
7877         SendTimeControl(cps, movesPerSession, timeControl,
7878                         timeIncrement, appData.searchDepth,
7879                         searchTime);
7880     }
7881     if (appData.showThinking 
7882         // [HGM] thinking: four options require thinking output to be sent
7883         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7884                                 ) {
7885         SendToProgram("post\n", cps);
7886     }
7887     SendToProgram("hard\n", cps);
7888     if (!appData.ponderNextMove) {
7889         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7890            it without being sure what state we are in first.  "hard"
7891            is not a toggle, so that one is OK.
7892          */
7893         SendToProgram("easy\n", cps);
7894     }
7895     if (cps->usePing) {
7896       sprintf(buf, "ping %d\n", ++cps->lastPing);
7897       SendToProgram(buf, cps);
7898     }
7899     cps->initDone = TRUE;
7900 }   
7901
7902
7903 void
7904 StartChessProgram(cps)
7905      ChessProgramState *cps;
7906 {
7907     char buf[MSG_SIZ];
7908     int err;
7909
7910     if (appData.noChessProgram) return;
7911     cps->initDone = FALSE;
7912
7913     if (strcmp(cps->host, "localhost") == 0) {
7914         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7915     } else if (*appData.remoteShell == NULLCHAR) {
7916         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7917     } else {
7918         if (*appData.remoteUser == NULLCHAR) {
7919           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7920                     cps->program);
7921         } else {
7922           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7923                     cps->host, appData.remoteUser, cps->program);
7924         }
7925         err = StartChildProcess(buf, "", &cps->pr);
7926     }
7927     
7928     if (err != 0) {
7929         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7930         DisplayFatalError(buf, err, 1);
7931         cps->pr = NoProc;
7932         cps->isr = NULL;
7933         return;
7934     }
7935     
7936     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7937     if (cps->protocolVersion > 1) {
7938       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7939       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7940       cps->comboCnt = 0;  //                and values of combo boxes
7941       SendToProgram(buf, cps);
7942     } else {
7943       SendToProgram("xboard\n", cps);
7944     }
7945 }
7946
7947
7948 void
7949 TwoMachinesEventIfReady P((void))
7950 {
7951   if (first.lastPing != first.lastPong) {
7952     DisplayMessage("", _("Waiting for first chess program"));
7953     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7954     return;
7955   }
7956   if (second.lastPing != second.lastPong) {
7957     DisplayMessage("", _("Waiting for second chess program"));
7958     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7959     return;
7960   }
7961   ThawUI();
7962   TwoMachinesEvent();
7963 }
7964
7965 void
7966 NextMatchGame P((void))
7967 {
7968     int index; /* [HGM] autoinc: step load index during match */
7969     Reset(FALSE, TRUE);
7970     if (*appData.loadGameFile != NULLCHAR) {
7971         index = appData.loadGameIndex;
7972         if(index < 0) { // [HGM] autoinc
7973             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7974             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7975         } 
7976         LoadGameFromFile(appData.loadGameFile,
7977                          index,
7978                          appData.loadGameFile, FALSE);
7979     } else if (*appData.loadPositionFile != NULLCHAR) {
7980         index = appData.loadPositionIndex;
7981         if(index < 0) { // [HGM] autoinc
7982             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7983             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7984         } 
7985         LoadPositionFromFile(appData.loadPositionFile,
7986                              index,
7987                              appData.loadPositionFile);
7988     }
7989     TwoMachinesEventIfReady();
7990 }
7991
7992 void UserAdjudicationEvent( int result )
7993 {
7994     ChessMove gameResult = GameIsDrawn;
7995
7996     if( result > 0 ) {
7997         gameResult = WhiteWins;
7998     }
7999     else if( result < 0 ) {
8000         gameResult = BlackWins;
8001     }
8002
8003     if( gameMode == TwoMachinesPlay ) {
8004         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8005     }
8006 }
8007
8008
8009 // [HGM] save: calculate checksum of game to make games easily identifiable
8010 int StringCheckSum(char *s)
8011 {
8012         int i = 0;
8013         if(s==NULL) return 0;
8014         while(*s) i = i*259 + *s++;
8015         return i;
8016 }
8017
8018 int GameCheckSum()
8019 {
8020         int i, sum=0;
8021         for(i=backwardMostMove; i<forwardMostMove; i++) {
8022                 sum += pvInfoList[i].depth;
8023                 sum += StringCheckSum(parseList[i]);
8024                 sum += StringCheckSum(commentList[i]);
8025                 sum *= 261;
8026         }
8027         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8028         return sum + StringCheckSum(commentList[i]);
8029 } // end of save patch
8030
8031 void
8032 GameEnds(result, resultDetails, whosays)
8033      ChessMove result;
8034      char *resultDetails;
8035      int whosays;
8036 {
8037     GameMode nextGameMode;
8038     int isIcsGame;
8039     char buf[MSG_SIZ];
8040
8041     if(endingGame) return; /* [HGM] crash: forbid recursion */
8042     endingGame = 1;
8043
8044     if (appData.debugMode) {
8045       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8046               result, resultDetails ? resultDetails : "(null)", whosays);
8047     }
8048
8049     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8050         /* If we are playing on ICS, the server decides when the
8051            game is over, but the engine can offer to draw, claim 
8052            a draw, or resign. 
8053          */
8054 #if ZIPPY
8055         if (appData.zippyPlay && first.initDone) {
8056             if (result == GameIsDrawn) {
8057                 /* In case draw still needs to be claimed */
8058                 SendToICS(ics_prefix);
8059                 SendToICS("draw\n");
8060             } else if (StrCaseStr(resultDetails, "resign")) {
8061                 SendToICS(ics_prefix);
8062                 SendToICS("resign\n");
8063             }
8064         }
8065 #endif
8066         endingGame = 0; /* [HGM] crash */
8067         return;
8068     }
8069
8070     /* If we're loading the game from a file, stop */
8071     if (whosays == GE_FILE) {
8072       (void) StopLoadGameTimer();
8073       gameFileFP = NULL;
8074     }
8075
8076     /* Cancel draw offers */
8077     first.offeredDraw = second.offeredDraw = 0;
8078
8079     /* If this is an ICS game, only ICS can really say it's done;
8080        if not, anyone can. */
8081     isIcsGame = (gameMode == IcsPlayingWhite || 
8082                  gameMode == IcsPlayingBlack || 
8083                  gameMode == IcsObserving    || 
8084                  gameMode == IcsExamining);
8085
8086     if (!isIcsGame || whosays == GE_ICS) {
8087         /* OK -- not an ICS game, or ICS said it was done */
8088         StopClocks();
8089         if (!isIcsGame && !appData.noChessProgram) 
8090           SetUserThinkingEnables();
8091     
8092         /* [HGM] if a machine claims the game end we verify this claim */
8093         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8094             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8095                 char claimer;
8096                 ChessMove trueResult = (ChessMove) -1;
8097
8098                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8099                                             first.twoMachinesColor[0] :
8100                                             second.twoMachinesColor[0] ;
8101
8102                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8103                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8104                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8105                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8106                 } else
8107                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8108                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8109                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8110                 } else
8111                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8112                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8113                 }
8114
8115                 // now verify win claims, but not in drop games, as we don't understand those yet
8116                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8117                                                  || gameInfo.variant == VariantGreat) &&
8118                     (result == WhiteWins && claimer == 'w' ||
8119                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8120                       if (appData.debugMode) {
8121                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8122                                 result, epStatus[forwardMostMove], forwardMostMove);
8123                       }
8124                       if(result != trueResult) {
8125                               sprintf(buf, "False win claim: '%s'", resultDetails);
8126                               result = claimer == 'w' ? BlackWins : WhiteWins;
8127                               resultDetails = buf;
8128                       }
8129                 } else
8130                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8131                     && (forwardMostMove <= backwardMostMove ||
8132                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8133                         (claimer=='b')==(forwardMostMove&1))
8134                                                                                   ) {
8135                       /* [HGM] verify: draws that were not flagged are false claims */
8136                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8137                       result = claimer == 'w' ? BlackWins : WhiteWins;
8138                       resultDetails = buf;
8139                 }
8140                 /* (Claiming a loss is accepted no questions asked!) */
8141             }
8142             /* [HGM] bare: don't allow bare King to win */
8143             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8144                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8145                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8146                && result != GameIsDrawn)
8147             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8148                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8149                         int p = (int)boards[forwardMostMove][i][j] - color;
8150                         if(p >= 0 && p <= (int)WhiteKing) k++;
8151                 }
8152                 if (appData.debugMode) {
8153                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8154                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8155                 }
8156                 if(k <= 1) {
8157                         result = GameIsDrawn;
8158                         sprintf(buf, "%s but bare king", resultDetails);
8159                         resultDetails = buf;
8160                 }
8161             }
8162         }
8163
8164
8165         if(serverMoves != NULL && !loadFlag) { char c = '=';
8166             if(result==WhiteWins) c = '+';
8167             if(result==BlackWins) c = '-';
8168             if(resultDetails != NULL)
8169                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8170         }
8171         if (resultDetails != NULL) {
8172             gameInfo.result = result;
8173             gameInfo.resultDetails = StrSave(resultDetails);
8174
8175             /* display last move only if game was not loaded from file */
8176             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8177                 DisplayMove(currentMove - 1);
8178     
8179             if (forwardMostMove != 0) {
8180                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8181                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8182                                                                 ) {
8183                     if (*appData.saveGameFile != NULLCHAR) {
8184                         SaveGameToFile(appData.saveGameFile, TRUE);
8185                     } else if (appData.autoSaveGames) {
8186                         AutoSaveGame();
8187                     }
8188                     if (*appData.savePositionFile != NULLCHAR) {
8189                         SavePositionToFile(appData.savePositionFile);
8190                     }
8191                 }
8192             }
8193
8194             /* Tell program how game ended in case it is learning */
8195             /* [HGM] Moved this to after saving the PGN, just in case */
8196             /* engine died and we got here through time loss. In that */
8197             /* case we will get a fatal error writing the pipe, which */
8198             /* would otherwise lose us the PGN.                       */
8199             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8200             /* output during GameEnds should never be fatal anymore   */
8201             if (gameMode == MachinePlaysWhite ||
8202                 gameMode == MachinePlaysBlack ||
8203                 gameMode == TwoMachinesPlay ||
8204                 gameMode == IcsPlayingWhite ||
8205                 gameMode == IcsPlayingBlack ||
8206                 gameMode == BeginningOfGame) {
8207                 char buf[MSG_SIZ];
8208                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8209                         resultDetails);
8210                 if (first.pr != NoProc) {
8211                     SendToProgram(buf, &first);
8212                 }
8213                 if (second.pr != NoProc &&
8214                     gameMode == TwoMachinesPlay) {
8215                     SendToProgram(buf, &second);
8216                 }
8217             }
8218         }
8219
8220         if (appData.icsActive) {
8221             if (appData.quietPlay &&
8222                 (gameMode == IcsPlayingWhite ||
8223                  gameMode == IcsPlayingBlack)) {
8224                 SendToICS(ics_prefix);
8225                 SendToICS("set shout 1\n");
8226             }
8227             nextGameMode = IcsIdle;
8228             ics_user_moved = FALSE;
8229             /* clean up premove.  It's ugly when the game has ended and the
8230              * premove highlights are still on the board.
8231              */
8232             if (gotPremove) {
8233               gotPremove = FALSE;
8234               ClearPremoveHighlights();
8235               DrawPosition(FALSE, boards[currentMove]);
8236             }
8237             if (whosays == GE_ICS) {
8238                 switch (result) {
8239                 case WhiteWins:
8240                     if (gameMode == IcsPlayingWhite)
8241                         PlayIcsWinSound();
8242                     else if(gameMode == IcsPlayingBlack)
8243                         PlayIcsLossSound();
8244                     break;
8245                 case BlackWins:
8246                     if (gameMode == IcsPlayingBlack)
8247                         PlayIcsWinSound();
8248                     else if(gameMode == IcsPlayingWhite)
8249                         PlayIcsLossSound();
8250                     break;
8251                 case GameIsDrawn:
8252                     PlayIcsDrawSound();
8253                     break;
8254                 default:
8255                     PlayIcsUnfinishedSound();
8256                 }
8257             }
8258         } else if (gameMode == EditGame ||
8259                    gameMode == PlayFromGameFile || 
8260                    gameMode == AnalyzeMode || 
8261                    gameMode == AnalyzeFile) {
8262             nextGameMode = gameMode;
8263         } else {
8264             nextGameMode = EndOfGame;
8265         }
8266         pausing = FALSE;
8267         ModeHighlight();
8268     } else {
8269         nextGameMode = gameMode;
8270     }
8271
8272     if (appData.noChessProgram) {
8273         gameMode = nextGameMode;
8274         ModeHighlight();
8275         endingGame = 0; /* [HGM] crash */
8276         return;
8277     }
8278
8279     if (first.reuse) {
8280         /* Put first chess program into idle state */
8281         if (first.pr != NoProc &&
8282             (gameMode == MachinePlaysWhite ||
8283              gameMode == MachinePlaysBlack ||
8284              gameMode == TwoMachinesPlay ||
8285              gameMode == IcsPlayingWhite ||
8286              gameMode == IcsPlayingBlack ||
8287              gameMode == BeginningOfGame)) {
8288             SendToProgram("force\n", &first);
8289             if (first.usePing) {
8290               char buf[MSG_SIZ];
8291               sprintf(buf, "ping %d\n", ++first.lastPing);
8292               SendToProgram(buf, &first);
8293             }
8294         }
8295     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8296         /* Kill off first chess program */
8297         if (first.isr != NULL)
8298           RemoveInputSource(first.isr);
8299         first.isr = NULL;
8300     
8301         if (first.pr != NoProc) {
8302             ExitAnalyzeMode();
8303             DoSleep( appData.delayBeforeQuit );
8304             SendToProgram("quit\n", &first);
8305             DoSleep( appData.delayAfterQuit );
8306             DestroyChildProcess(first.pr, first.useSigterm);
8307         }
8308         first.pr = NoProc;
8309     }
8310     if (second.reuse) {
8311         /* Put second chess program into idle state */
8312         if (second.pr != NoProc &&
8313             gameMode == TwoMachinesPlay) {
8314             SendToProgram("force\n", &second);
8315             if (second.usePing) {
8316               char buf[MSG_SIZ];
8317               sprintf(buf, "ping %d\n", ++second.lastPing);
8318               SendToProgram(buf, &second);
8319             }
8320         }
8321     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8322         /* Kill off second chess program */
8323         if (second.isr != NULL)
8324           RemoveInputSource(second.isr);
8325         second.isr = NULL;
8326     
8327         if (second.pr != NoProc) {
8328             DoSleep( appData.delayBeforeQuit );
8329             SendToProgram("quit\n", &second);
8330             DoSleep( appData.delayAfterQuit );
8331             DestroyChildProcess(second.pr, second.useSigterm);
8332         }
8333         second.pr = NoProc;
8334     }
8335
8336     if (matchMode && gameMode == TwoMachinesPlay) {
8337         switch (result) {
8338         case WhiteWins:
8339           if (first.twoMachinesColor[0] == 'w') {
8340             first.matchWins++;
8341           } else {
8342             second.matchWins++;
8343           }
8344           break;
8345         case BlackWins:
8346           if (first.twoMachinesColor[0] == 'b') {
8347             first.matchWins++;
8348           } else {
8349             second.matchWins++;
8350           }
8351           break;
8352         default:
8353           break;
8354         }
8355         if (matchGame < appData.matchGames) {
8356             char *tmp;
8357             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8358                 tmp = first.twoMachinesColor;
8359                 first.twoMachinesColor = second.twoMachinesColor;
8360                 second.twoMachinesColor = tmp;
8361             }
8362             gameMode = nextGameMode;
8363             matchGame++;
8364             if(appData.matchPause>10000 || appData.matchPause<10)
8365                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8366             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8367             endingGame = 0; /* [HGM] crash */
8368             return;
8369         } else {
8370             char buf[MSG_SIZ];
8371             gameMode = nextGameMode;
8372             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8373                     first.tidy, second.tidy,
8374                     first.matchWins, second.matchWins,
8375                     appData.matchGames - (first.matchWins + second.matchWins));
8376             DisplayFatalError(buf, 0, 0);
8377         }
8378     }
8379     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8380         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8381       ExitAnalyzeMode();
8382     gameMode = nextGameMode;
8383     ModeHighlight();
8384     endingGame = 0;  /* [HGM] crash */
8385 }
8386
8387 /* Assumes program was just initialized (initString sent).
8388    Leaves program in force mode. */
8389 void
8390 FeedMovesToProgram(cps, upto) 
8391      ChessProgramState *cps;
8392      int upto;
8393 {
8394     int i;
8395     
8396     if (appData.debugMode)
8397       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8398               startedFromSetupPosition ? "position and " : "",
8399               backwardMostMove, upto, cps->which);
8400     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8401         // [HGM] variantswitch: make engine aware of new variant
8402         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8403                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8404         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8405         SendToProgram(buf, cps);
8406         currentlyInitializedVariant = gameInfo.variant;
8407     }
8408     SendToProgram("force\n", cps);
8409     if (startedFromSetupPosition) {
8410         SendBoard(cps, backwardMostMove);
8411     if (appData.debugMode) {
8412         fprintf(debugFP, "feedMoves\n");
8413     }
8414     }
8415     for (i = backwardMostMove; i < upto; i++) {
8416         SendMoveToProgram(i, cps);
8417     }
8418 }
8419
8420
8421 void
8422 ResurrectChessProgram()
8423 {
8424      /* The chess program may have exited.
8425         If so, restart it and feed it all the moves made so far. */
8426
8427     if (appData.noChessProgram || first.pr != NoProc) return;
8428     
8429     StartChessProgram(&first);
8430     InitChessProgram(&first, FALSE);
8431     FeedMovesToProgram(&first, currentMove);
8432
8433     if (!first.sendTime) {
8434         /* can't tell gnuchess what its clock should read,
8435            so we bow to its notion. */
8436         ResetClocks();
8437         timeRemaining[0][currentMove] = whiteTimeRemaining;
8438         timeRemaining[1][currentMove] = blackTimeRemaining;
8439     }
8440
8441     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8442                 appData.icsEngineAnalyze) && first.analysisSupport) {
8443       SendToProgram("analyze\n", &first);
8444       first.analyzing = TRUE;
8445     }
8446 }
8447
8448 /*
8449  * Button procedures
8450  */
8451 void
8452 Reset(redraw, init)
8453      int redraw, init;
8454 {
8455     int i;
8456
8457     if (appData.debugMode) {
8458         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8459                 redraw, init, gameMode);
8460     }
8461     pausing = pauseExamInvalid = FALSE;
8462     startedFromSetupPosition = blackPlaysFirst = FALSE;
8463     firstMove = TRUE;
8464     whiteFlag = blackFlag = FALSE;
8465     userOfferedDraw = FALSE;
8466     hintRequested = bookRequested = FALSE;
8467     first.maybeThinking = FALSE;
8468     second.maybeThinking = FALSE;
8469     first.bookSuspend = FALSE; // [HGM] book
8470     second.bookSuspend = FALSE;
8471     thinkOutput[0] = NULLCHAR;
8472     lastHint[0] = NULLCHAR;
8473     ClearGameInfo(&gameInfo);
8474     gameInfo.variant = StringToVariant(appData.variant);
8475     ics_user_moved = ics_clock_paused = FALSE;
8476     ics_getting_history = H_FALSE;
8477     ics_gamenum = -1;
8478     white_holding[0] = black_holding[0] = NULLCHAR;
8479     ClearProgramStats();
8480     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8481     
8482     ResetFrontEnd();
8483     ClearHighlights();
8484     flipView = appData.flipView;
8485     ClearPremoveHighlights();
8486     gotPremove = FALSE;
8487     alarmSounded = FALSE;
8488
8489     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8490     if(appData.serverMovesName != NULL) {
8491         /* [HGM] prepare to make moves file for broadcasting */
8492         clock_t t = clock();
8493         if(serverMoves != NULL) fclose(serverMoves);
8494         serverMoves = fopen(appData.serverMovesName, "r");
8495         if(serverMoves != NULL) {
8496             fclose(serverMoves);
8497             /* delay 15 sec before overwriting, so all clients can see end */
8498             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8499         }
8500         serverMoves = fopen(appData.serverMovesName, "w");
8501     }
8502
8503     ExitAnalyzeMode();
8504     gameMode = BeginningOfGame;
8505     ModeHighlight();
8506     if(appData.icsActive) gameInfo.variant = VariantNormal;
8507     currentMove = forwardMostMove = backwardMostMove = 0;
8508     InitPosition(redraw);
8509     for (i = 0; i < MAX_MOVES; i++) {
8510         if (commentList[i] != NULL) {
8511             free(commentList[i]);
8512             commentList[i] = NULL;
8513         }
8514     }
8515     ResetClocks();
8516     timeRemaining[0][0] = whiteTimeRemaining;
8517     timeRemaining[1][0] = blackTimeRemaining;
8518     if (first.pr == NULL) {
8519         StartChessProgram(&first);
8520     }
8521     if (init) {
8522             InitChessProgram(&first, startedFromSetupPosition);
8523     }
8524     DisplayTitle("");
8525     DisplayMessage("", "");
8526     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8527     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8528 }
8529
8530 void
8531 AutoPlayGameLoop()
8532 {
8533     for (;;) {
8534         if (!AutoPlayOneMove())
8535           return;
8536         if (matchMode || appData.timeDelay == 0)
8537           continue;
8538         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8539           return;
8540         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8541         break;
8542     }
8543 }
8544
8545
8546 int
8547 AutoPlayOneMove()
8548 {
8549     int fromX, fromY, toX, toY;
8550
8551     if (appData.debugMode) {
8552       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8553     }
8554
8555     if (gameMode != PlayFromGameFile)
8556       return FALSE;
8557
8558     if (currentMove >= forwardMostMove) {
8559       gameMode = EditGame;
8560       ModeHighlight();
8561
8562       /* [AS] Clear current move marker at the end of a game */
8563       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8564
8565       return FALSE;
8566     }
8567     
8568     toX = moveList[currentMove][2] - AAA;
8569     toY = moveList[currentMove][3] - ONE;
8570
8571     if (moveList[currentMove][1] == '@') {
8572         if (appData.highlightLastMove) {
8573             SetHighlights(-1, -1, toX, toY);
8574         }
8575     } else {
8576         fromX = moveList[currentMove][0] - AAA;
8577         fromY = moveList[currentMove][1] - ONE;
8578
8579         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8580
8581         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8582
8583         if (appData.highlightLastMove) {
8584             SetHighlights(fromX, fromY, toX, toY);
8585         }
8586     }
8587     DisplayMove(currentMove);
8588     SendMoveToProgram(currentMove++, &first);
8589     DisplayBothClocks();
8590     DrawPosition(FALSE, boards[currentMove]);
8591     // [HGM] PV info: always display, routine tests if empty
8592     DisplayComment(currentMove - 1, commentList[currentMove]);
8593     return TRUE;
8594 }
8595
8596
8597 int
8598 LoadGameOneMove(readAhead)
8599      ChessMove readAhead;
8600 {
8601     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8602     char promoChar = NULLCHAR;
8603     ChessMove moveType;
8604     char move[MSG_SIZ];
8605     char *p, *q;
8606     
8607     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8608         gameMode != AnalyzeMode && gameMode != Training) {
8609         gameFileFP = NULL;
8610         return FALSE;
8611     }
8612     
8613     yyboardindex = forwardMostMove;
8614     if (readAhead != (ChessMove)0) {
8615       moveType = readAhead;
8616     } else {
8617       if (gameFileFP == NULL)
8618           return FALSE;
8619       moveType = (ChessMove) yylex();
8620     }
8621     
8622     done = FALSE;
8623     switch (moveType) {
8624       case Comment:
8625         if (appData.debugMode) 
8626           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8627         p = yy_text;
8628         if (*p == '{' || *p == '[' || *p == '(') {
8629             p[strlen(p) - 1] = NULLCHAR;
8630             p++;
8631         }
8632
8633         /* append the comment but don't display it */
8634         while (*p == '\n') p++;
8635         AppendComment(currentMove, p);
8636         return TRUE;
8637
8638       case WhiteCapturesEnPassant:
8639       case BlackCapturesEnPassant:
8640       case WhitePromotionChancellor:
8641       case BlackPromotionChancellor:
8642       case WhitePromotionArchbishop:
8643       case BlackPromotionArchbishop:
8644       case WhitePromotionCentaur:
8645       case BlackPromotionCentaur:
8646       case WhitePromotionQueen:
8647       case BlackPromotionQueen:
8648       case WhitePromotionRook:
8649       case BlackPromotionRook:
8650       case WhitePromotionBishop:
8651       case BlackPromotionBishop:
8652       case WhitePromotionKnight:
8653       case BlackPromotionKnight:
8654       case WhitePromotionKing:
8655       case BlackPromotionKing:
8656       case NormalMove:
8657       case WhiteKingSideCastle:
8658       case WhiteQueenSideCastle:
8659       case BlackKingSideCastle:
8660       case BlackQueenSideCastle:
8661       case WhiteKingSideCastleWild:
8662       case WhiteQueenSideCastleWild:
8663       case BlackKingSideCastleWild:
8664       case BlackQueenSideCastleWild:
8665       /* PUSH Fabien */
8666       case WhiteHSideCastleFR:
8667       case WhiteASideCastleFR:
8668       case BlackHSideCastleFR:
8669       case BlackASideCastleFR:
8670       /* POP Fabien */
8671         if (appData.debugMode)
8672           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8673         fromX = currentMoveString[0] - AAA;
8674         fromY = currentMoveString[1] - ONE;
8675         toX = currentMoveString[2] - AAA;
8676         toY = currentMoveString[3] - ONE;
8677         promoChar = currentMoveString[4];
8678         break;
8679
8680       case WhiteDrop:
8681       case BlackDrop:
8682         if (appData.debugMode)
8683           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8684         fromX = moveType == WhiteDrop ?
8685           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8686         (int) CharToPiece(ToLower(currentMoveString[0]));
8687         fromY = DROP_RANK;
8688         toX = currentMoveString[2] - AAA;
8689         toY = currentMoveString[3] - ONE;
8690         break;
8691
8692       case WhiteWins:
8693       case BlackWins:
8694       case GameIsDrawn:
8695       case GameUnfinished:
8696         if (appData.debugMode)
8697           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8698         p = strchr(yy_text, '{');
8699         if (p == NULL) p = strchr(yy_text, '(');
8700         if (p == NULL) {
8701             p = yy_text;
8702             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8703         } else {
8704             q = strchr(p, *p == '{' ? '}' : ')');
8705             if (q != NULL) *q = NULLCHAR;
8706             p++;
8707         }
8708         GameEnds(moveType, p, GE_FILE);
8709         done = TRUE;
8710         if (cmailMsgLoaded) {
8711             ClearHighlights();
8712             flipView = WhiteOnMove(currentMove);
8713             if (moveType == GameUnfinished) flipView = !flipView;
8714             if (appData.debugMode)
8715               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8716         }
8717         break;
8718
8719       case (ChessMove) 0:       /* end of file */
8720         if (appData.debugMode)
8721           fprintf(debugFP, "Parser hit end of file\n");
8722         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8723                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8724           case MT_NONE:
8725           case MT_CHECK:
8726             break;
8727           case MT_CHECKMATE:
8728           case MT_STAINMATE:
8729             if (WhiteOnMove(currentMove)) {
8730                 GameEnds(BlackWins, "Black mates", GE_FILE);
8731             } else {
8732                 GameEnds(WhiteWins, "White mates", GE_FILE);
8733             }
8734             break;
8735           case MT_STALEMATE:
8736             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8737             break;
8738         }
8739         done = TRUE;
8740         break;
8741
8742       case MoveNumberOne:
8743         if (lastLoadGameStart == GNUChessGame) {
8744             /* GNUChessGames have numbers, but they aren't move numbers */
8745             if (appData.debugMode)
8746               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8747                       yy_text, (int) moveType);
8748             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8749         }
8750         /* else fall thru */
8751
8752       case XBoardGame:
8753       case GNUChessGame:
8754       case PGNTag:
8755         /* Reached start of next game in file */
8756         if (appData.debugMode)
8757           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8758         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8759                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8760           case MT_NONE:
8761           case MT_CHECK:
8762             break;
8763           case MT_CHECKMATE:
8764           case MT_STAINMATE:
8765             if (WhiteOnMove(currentMove)) {
8766                 GameEnds(BlackWins, "Black mates", GE_FILE);
8767             } else {
8768                 GameEnds(WhiteWins, "White mates", GE_FILE);
8769             }
8770             break;
8771           case MT_STALEMATE:
8772             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8773             break;
8774         }
8775         done = TRUE;
8776         break;
8777
8778       case PositionDiagram:     /* should not happen; ignore */
8779       case ElapsedTime:         /* ignore */
8780       case NAG:                 /* ignore */
8781         if (appData.debugMode)
8782           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8783                   yy_text, (int) moveType);
8784         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8785
8786       case IllegalMove:
8787         if (appData.testLegality) {
8788             if (appData.debugMode)
8789               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8790             sprintf(move, _("Illegal move: %d.%s%s"),
8791                     (forwardMostMove / 2) + 1,
8792                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8793             DisplayError(move, 0);
8794             done = TRUE;
8795         } else {
8796             if (appData.debugMode)
8797               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8798                       yy_text, currentMoveString);
8799             fromX = currentMoveString[0] - AAA;
8800             fromY = currentMoveString[1] - ONE;
8801             toX = currentMoveString[2] - AAA;
8802             toY = currentMoveString[3] - ONE;
8803             promoChar = currentMoveString[4];
8804         }
8805         break;
8806
8807       case AmbiguousMove:
8808         if (appData.debugMode)
8809           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8810         sprintf(move, _("Ambiguous move: %d.%s%s"),
8811                 (forwardMostMove / 2) + 1,
8812                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8813         DisplayError(move, 0);
8814         done = TRUE;
8815         break;
8816
8817       default:
8818       case ImpossibleMove:
8819         if (appData.debugMode)
8820           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8821         sprintf(move, _("Illegal move: %d.%s%s"),
8822                 (forwardMostMove / 2) + 1,
8823                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8824         DisplayError(move, 0);
8825         done = TRUE;
8826         break;
8827     }
8828
8829     if (done) {
8830         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8831             DrawPosition(FALSE, boards[currentMove]);
8832             DisplayBothClocks();
8833             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8834               DisplayComment(currentMove - 1, commentList[currentMove]);
8835         }
8836         (void) StopLoadGameTimer();
8837         gameFileFP = NULL;
8838         cmailOldMove = forwardMostMove;
8839         return FALSE;
8840     } else {
8841         /* currentMoveString is set as a side-effect of yylex */
8842         strcat(currentMoveString, "\n");
8843         strcpy(moveList[forwardMostMove], currentMoveString);
8844         
8845         thinkOutput[0] = NULLCHAR;
8846         MakeMove(fromX, fromY, toX, toY, promoChar);
8847         currentMove = forwardMostMove;
8848         return TRUE;
8849     }
8850 }
8851
8852 /* Load the nth game from the given file */
8853 int
8854 LoadGameFromFile(filename, n, title, useList)
8855      char *filename;
8856      int n;
8857      char *title;
8858      /*Boolean*/ int useList;
8859 {
8860     FILE *f;
8861     char buf[MSG_SIZ];
8862
8863     if (strcmp(filename, "-") == 0) {
8864         f = stdin;
8865         title = "stdin";
8866     } else {
8867         f = fopen(filename, "rb");
8868         if (f == NULL) {
8869           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8870             DisplayError(buf, errno);
8871             return FALSE;
8872         }
8873     }
8874     if (fseek(f, 0, 0) == -1) {
8875         /* f is not seekable; probably a pipe */
8876         useList = FALSE;
8877     }
8878     if (useList && n == 0) {
8879         int error = GameListBuild(f);
8880         if (error) {
8881             DisplayError(_("Cannot build game list"), error);
8882         } else if (!ListEmpty(&gameList) &&
8883                    ((ListGame *) gameList.tailPred)->number > 1) {
8884             GameListPopUp(f, title);
8885             return TRUE;
8886         }
8887         GameListDestroy();
8888         n = 1;
8889     }
8890     if (n == 0) n = 1;
8891     return LoadGame(f, n, title, FALSE);
8892 }
8893
8894
8895 void
8896 MakeRegisteredMove()
8897 {
8898     int fromX, fromY, toX, toY;
8899     char promoChar;
8900     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8901         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8902           case CMAIL_MOVE:
8903           case CMAIL_DRAW:
8904             if (appData.debugMode)
8905               fprintf(debugFP, "Restoring %s for game %d\n",
8906                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8907     
8908             thinkOutput[0] = NULLCHAR;
8909             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8910             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8911             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8912             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8913             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8914             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8915             MakeMove(fromX, fromY, toX, toY, promoChar);
8916             ShowMove(fromX, fromY, toX, toY);
8917               
8918             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8919                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8920               case MT_NONE:
8921               case MT_CHECK:
8922                 break;
8923                 
8924               case MT_CHECKMATE:
8925               case MT_STAINMATE:
8926                 if (WhiteOnMove(currentMove)) {
8927                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8928                 } else {
8929                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8930                 }
8931                 break;
8932                 
8933               case MT_STALEMATE:
8934                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8935                 break;
8936             }
8937
8938             break;
8939             
8940           case CMAIL_RESIGN:
8941             if (WhiteOnMove(currentMove)) {
8942                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8943             } else {
8944                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8945             }
8946             break;
8947             
8948           case CMAIL_ACCEPT:
8949             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8950             break;
8951               
8952           default:
8953             break;
8954         }
8955     }
8956
8957     return;
8958 }
8959
8960 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8961 int
8962 CmailLoadGame(f, gameNumber, title, useList)
8963      FILE *f;
8964      int gameNumber;
8965      char *title;
8966      int useList;
8967 {
8968     int retVal;
8969
8970     if (gameNumber > nCmailGames) {
8971         DisplayError(_("No more games in this message"), 0);
8972         return FALSE;
8973     }
8974     if (f == lastLoadGameFP) {
8975         int offset = gameNumber - lastLoadGameNumber;
8976         if (offset == 0) {
8977             cmailMsg[0] = NULLCHAR;
8978             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8979                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8980                 nCmailMovesRegistered--;
8981             }
8982             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8983             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8984                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8985             }
8986         } else {
8987             if (! RegisterMove()) return FALSE;
8988         }
8989     }
8990
8991     retVal = LoadGame(f, gameNumber, title, useList);
8992
8993     /* Make move registered during previous look at this game, if any */
8994     MakeRegisteredMove();
8995
8996     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8997         commentList[currentMove]
8998           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8999         DisplayComment(currentMove - 1, commentList[currentMove]);
9000     }
9001
9002     return retVal;
9003 }
9004
9005 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9006 int
9007 ReloadGame(offset)
9008      int offset;
9009 {
9010     int gameNumber = lastLoadGameNumber + offset;
9011     if (lastLoadGameFP == NULL) {
9012         DisplayError(_("No game has been loaded yet"), 0);
9013         return FALSE;
9014     }
9015     if (gameNumber <= 0) {
9016         DisplayError(_("Can't back up any further"), 0);
9017         return FALSE;
9018     }
9019     if (cmailMsgLoaded) {
9020         return CmailLoadGame(lastLoadGameFP, gameNumber,
9021                              lastLoadGameTitle, lastLoadGameUseList);
9022     } else {
9023         return LoadGame(lastLoadGameFP, gameNumber,
9024                         lastLoadGameTitle, lastLoadGameUseList);
9025     }
9026 }
9027
9028
9029
9030 /* Load the nth game from open file f */
9031 int
9032 LoadGame(f, gameNumber, title, useList)
9033      FILE *f;
9034      int gameNumber;
9035      char *title;
9036      int useList;
9037 {
9038     ChessMove cm;
9039     char buf[MSG_SIZ];
9040     int gn = gameNumber;
9041     ListGame *lg = NULL;
9042     int numPGNTags = 0;
9043     int err;
9044     GameMode oldGameMode;
9045     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9046
9047     if (appData.debugMode) 
9048         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9049
9050     if (gameMode == Training )
9051         SetTrainingModeOff();
9052
9053     oldGameMode = gameMode;
9054     if (gameMode != BeginningOfGame) {
9055       Reset(FALSE, TRUE);
9056     }
9057
9058     gameFileFP = f;
9059     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9060         fclose(lastLoadGameFP);
9061     }
9062
9063     if (useList) {
9064         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9065         
9066         if (lg) {
9067             fseek(f, lg->offset, 0);
9068             GameListHighlight(gameNumber);
9069             gn = 1;
9070         }
9071         else {
9072             DisplayError(_("Game number out of range"), 0);
9073             return FALSE;
9074         }
9075     } else {
9076         GameListDestroy();
9077         if (fseek(f, 0, 0) == -1) {
9078             if (f == lastLoadGameFP ?
9079                 gameNumber == lastLoadGameNumber + 1 :
9080                 gameNumber == 1) {
9081                 gn = 1;
9082             } else {
9083                 DisplayError(_("Can't seek on game file"), 0);
9084                 return FALSE;
9085             }
9086         }
9087     }
9088     lastLoadGameFP = f;
9089     lastLoadGameNumber = gameNumber;
9090     strcpy(lastLoadGameTitle, title);
9091     lastLoadGameUseList = useList;
9092
9093     yynewfile(f);
9094
9095     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9096       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9097                 lg->gameInfo.black);
9098             DisplayTitle(buf);
9099     } else if (*title != NULLCHAR) {
9100         if (gameNumber > 1) {
9101             sprintf(buf, "%s %d", title, gameNumber);
9102             DisplayTitle(buf);
9103         } else {
9104             DisplayTitle(title);
9105         }
9106     }
9107
9108     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9109         gameMode = PlayFromGameFile;
9110         ModeHighlight();
9111     }
9112
9113     currentMove = forwardMostMove = backwardMostMove = 0;
9114     CopyBoard(boards[0], initialPosition);
9115     StopClocks();
9116
9117     /*
9118      * Skip the first gn-1 games in the file.
9119      * Also skip over anything that precedes an identifiable 
9120      * start of game marker, to avoid being confused by 
9121      * garbage at the start of the file.  Currently 
9122      * recognized start of game markers are the move number "1",
9123      * the pattern "gnuchess .* game", the pattern
9124      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9125      * A game that starts with one of the latter two patterns
9126      * will also have a move number 1, possibly
9127      * following a position diagram.
9128      * 5-4-02: Let's try being more lenient and allowing a game to
9129      * start with an unnumbered move.  Does that break anything?
9130      */
9131     cm = lastLoadGameStart = (ChessMove) 0;
9132     while (gn > 0) {
9133         yyboardindex = forwardMostMove;
9134         cm = (ChessMove) yylex();
9135         switch (cm) {
9136           case (ChessMove) 0:
9137             if (cmailMsgLoaded) {
9138                 nCmailGames = CMAIL_MAX_GAMES - gn;
9139             } else {
9140                 Reset(TRUE, TRUE);
9141                 DisplayError(_("Game not found in file"), 0);
9142             }
9143             return FALSE;
9144
9145           case GNUChessGame:
9146           case XBoardGame:
9147             gn--;
9148             lastLoadGameStart = cm;
9149             break;
9150             
9151           case MoveNumberOne:
9152             switch (lastLoadGameStart) {
9153               case GNUChessGame:
9154               case XBoardGame:
9155               case PGNTag:
9156                 break;
9157               case MoveNumberOne:
9158               case (ChessMove) 0:
9159                 gn--;           /* count this game */
9160                 lastLoadGameStart = cm;
9161                 break;
9162               default:
9163                 /* impossible */
9164                 break;
9165             }
9166             break;
9167
9168           case PGNTag:
9169             switch (lastLoadGameStart) {
9170               case GNUChessGame:
9171               case PGNTag:
9172               case MoveNumberOne:
9173               case (ChessMove) 0:
9174                 gn--;           /* count this game */
9175                 lastLoadGameStart = cm;
9176                 break;
9177               case XBoardGame:
9178                 lastLoadGameStart = cm; /* game counted already */
9179                 break;
9180               default:
9181                 /* impossible */
9182                 break;
9183             }
9184             if (gn > 0) {
9185                 do {
9186                     yyboardindex = forwardMostMove;
9187                     cm = (ChessMove) yylex();
9188                 } while (cm == PGNTag || cm == Comment);
9189             }
9190             break;
9191
9192           case WhiteWins:
9193           case BlackWins:
9194           case GameIsDrawn:
9195             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9196                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9197                     != CMAIL_OLD_RESULT) {
9198                     nCmailResults ++ ;
9199                     cmailResult[  CMAIL_MAX_GAMES
9200                                 - gn - 1] = CMAIL_OLD_RESULT;
9201                 }
9202             }
9203             break;
9204
9205           case NormalMove:
9206             /* Only a NormalMove can be at the start of a game
9207              * without a position diagram. */
9208             if (lastLoadGameStart == (ChessMove) 0) {
9209               gn--;
9210               lastLoadGameStart = MoveNumberOne;
9211             }
9212             break;
9213
9214           default:
9215             break;
9216         }
9217     }
9218     
9219     if (appData.debugMode)
9220       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9221
9222     if (cm == XBoardGame) {
9223         /* Skip any header junk before position diagram and/or move 1 */
9224         for (;;) {
9225             yyboardindex = forwardMostMove;
9226             cm = (ChessMove) yylex();
9227
9228             if (cm == (ChessMove) 0 ||
9229                 cm == GNUChessGame || cm == XBoardGame) {
9230                 /* Empty game; pretend end-of-file and handle later */
9231                 cm = (ChessMove) 0;
9232                 break;
9233             }
9234
9235             if (cm == MoveNumberOne || cm == PositionDiagram ||
9236                 cm == PGNTag || cm == Comment)
9237               break;
9238         }
9239     } else if (cm == GNUChessGame) {
9240         if (gameInfo.event != NULL) {
9241             free(gameInfo.event);
9242         }
9243         gameInfo.event = StrSave(yy_text);
9244     }   
9245
9246     startedFromSetupPosition = FALSE;
9247     while (cm == PGNTag) {
9248         if (appData.debugMode) 
9249           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9250         err = ParsePGNTag(yy_text, &gameInfo);
9251         if (!err) numPGNTags++;
9252
9253         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9254         if(gameInfo.variant != oldVariant) {
9255             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9256             InitPosition(TRUE);
9257             oldVariant = gameInfo.variant;
9258             if (appData.debugMode) 
9259               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9260         }
9261
9262
9263         if (gameInfo.fen != NULL) {
9264           Board initial_position;
9265           startedFromSetupPosition = TRUE;
9266           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9267             Reset(TRUE, TRUE);
9268             DisplayError(_("Bad FEN position in file"), 0);
9269             return FALSE;
9270           }
9271           CopyBoard(boards[0], initial_position);
9272           if (blackPlaysFirst) {
9273             currentMove = forwardMostMove = backwardMostMove = 1;
9274             CopyBoard(boards[1], initial_position);
9275             strcpy(moveList[0], "");
9276             strcpy(parseList[0], "");
9277             timeRemaining[0][1] = whiteTimeRemaining;
9278             timeRemaining[1][1] = blackTimeRemaining;
9279             if (commentList[0] != NULL) {
9280               commentList[1] = commentList[0];
9281               commentList[0] = NULL;
9282             }
9283           } else {
9284             currentMove = forwardMostMove = backwardMostMove = 0;
9285           }
9286           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9287           {   int i;
9288               initialRulePlies = FENrulePlies;
9289               epStatus[forwardMostMove] = FENepStatus;
9290               for( i=0; i< nrCastlingRights; i++ )
9291                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9292           }
9293           yyboardindex = forwardMostMove;
9294           free(gameInfo.fen);
9295           gameInfo.fen = NULL;
9296         }
9297
9298         yyboardindex = forwardMostMove;
9299         cm = (ChessMove) yylex();
9300
9301         /* Handle comments interspersed among the tags */
9302         while (cm == Comment) {
9303             char *p;
9304             if (appData.debugMode) 
9305               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9306             p = yy_text;
9307             if (*p == '{' || *p == '[' || *p == '(') {
9308                 p[strlen(p) - 1] = NULLCHAR;
9309                 p++;
9310             }
9311             while (*p == '\n') p++;
9312             AppendComment(currentMove, p);
9313             yyboardindex = forwardMostMove;
9314             cm = (ChessMove) yylex();
9315         }
9316     }
9317
9318     /* don't rely on existence of Event tag since if game was
9319      * pasted from clipboard the Event tag may not exist
9320      */
9321     if (numPGNTags > 0){
9322         char *tags;
9323         if (gameInfo.variant == VariantNormal) {
9324           gameInfo.variant = StringToVariant(gameInfo.event);
9325         }
9326         if (!matchMode) {
9327           if( appData.autoDisplayTags ) {
9328             tags = PGNTags(&gameInfo);
9329             TagsPopUp(tags, CmailMsg());
9330             free(tags);
9331           }
9332         }
9333     } else {
9334         /* Make something up, but don't display it now */
9335         SetGameInfo();
9336         TagsPopDown();
9337     }
9338
9339     if (cm == PositionDiagram) {
9340         int i, j;
9341         char *p;
9342         Board initial_position;
9343
9344         if (appData.debugMode)
9345           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9346
9347         if (!startedFromSetupPosition) {
9348             p = yy_text;
9349             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9350               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9351                 switch (*p) {
9352                   case '[':
9353                   case '-':
9354                   case ' ':
9355                   case '\t':
9356                   case '\n':
9357                   case '\r':
9358                     break;
9359                   default:
9360                     initial_position[i][j++] = CharToPiece(*p);
9361                     break;
9362                 }
9363             while (*p == ' ' || *p == '\t' ||
9364                    *p == '\n' || *p == '\r') p++;
9365         
9366             if (strncmp(p, "black", strlen("black"))==0)
9367               blackPlaysFirst = TRUE;
9368             else
9369               blackPlaysFirst = FALSE;
9370             startedFromSetupPosition = TRUE;
9371         
9372             CopyBoard(boards[0], initial_position);
9373             if (blackPlaysFirst) {
9374                 currentMove = forwardMostMove = backwardMostMove = 1;
9375                 CopyBoard(boards[1], initial_position);
9376                 strcpy(moveList[0], "");
9377                 strcpy(parseList[0], "");
9378                 timeRemaining[0][1] = whiteTimeRemaining;
9379                 timeRemaining[1][1] = blackTimeRemaining;
9380                 if (commentList[0] != NULL) {
9381                     commentList[1] = commentList[0];
9382                     commentList[0] = NULL;
9383                 }
9384             } else {
9385                 currentMove = forwardMostMove = backwardMostMove = 0;
9386             }
9387         }
9388         yyboardindex = forwardMostMove;
9389         cm = (ChessMove) yylex();
9390     }
9391
9392     if (first.pr == NoProc) {
9393         StartChessProgram(&first);
9394     }
9395     InitChessProgram(&first, FALSE);
9396     SendToProgram("force\n", &first);
9397     if (startedFromSetupPosition) {
9398         SendBoard(&first, forwardMostMove);
9399     if (appData.debugMode) {
9400         fprintf(debugFP, "Load Game\n");
9401     }
9402         DisplayBothClocks();
9403     }      
9404
9405     /* [HGM] server: flag to write setup moves in broadcast file as one */
9406     loadFlag = appData.suppressLoadMoves;
9407
9408     while (cm == Comment) {
9409         char *p;
9410         if (appData.debugMode) 
9411           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9412         p = yy_text;
9413         if (*p == '{' || *p == '[' || *p == '(') {
9414             p[strlen(p) - 1] = NULLCHAR;
9415             p++;
9416         }
9417         while (*p == '\n') p++;
9418         AppendComment(currentMove, p);
9419         yyboardindex = forwardMostMove;
9420         cm = (ChessMove) yylex();
9421     }
9422
9423     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9424         cm == WhiteWins || cm == BlackWins ||
9425         cm == GameIsDrawn || cm == GameUnfinished) {
9426         DisplayMessage("", _("No moves in game"));
9427         if (cmailMsgLoaded) {
9428             if (appData.debugMode)
9429               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9430             ClearHighlights();
9431             flipView = FALSE;
9432         }
9433         DrawPosition(FALSE, boards[currentMove]);
9434         DisplayBothClocks();
9435         gameMode = EditGame;
9436         ModeHighlight();
9437         gameFileFP = NULL;
9438         cmailOldMove = 0;
9439         return TRUE;
9440     }
9441
9442     // [HGM] PV info: routine tests if comment empty
9443     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9444         DisplayComment(currentMove - 1, commentList[currentMove]);
9445     }
9446     if (!matchMode && appData.timeDelay != 0) 
9447       DrawPosition(FALSE, boards[currentMove]);
9448
9449     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9450       programStats.ok_to_send = 1;
9451     }
9452
9453     /* if the first token after the PGN tags is a move
9454      * and not move number 1, retrieve it from the parser 
9455      */
9456     if (cm != MoveNumberOne)
9457         LoadGameOneMove(cm);
9458
9459     /* load the remaining moves from the file */
9460     while (LoadGameOneMove((ChessMove)0)) {
9461       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9462       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9463     }
9464
9465     /* rewind to the start of the game */
9466     currentMove = backwardMostMove;
9467
9468     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9469
9470     if (oldGameMode == AnalyzeFile ||
9471         oldGameMode == AnalyzeMode) {
9472       AnalyzeFileEvent();
9473     }
9474
9475     if (matchMode || appData.timeDelay == 0) {
9476       ToEndEvent();
9477       gameMode = EditGame;
9478       ModeHighlight();
9479     } else if (appData.timeDelay > 0) {
9480       AutoPlayGameLoop();
9481     }
9482
9483     if (appData.debugMode) 
9484         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9485
9486     loadFlag = 0; /* [HGM] true game starts */
9487     return TRUE;
9488 }
9489
9490 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9491 int
9492 ReloadPosition(offset)
9493      int offset;
9494 {
9495     int positionNumber = lastLoadPositionNumber + offset;
9496     if (lastLoadPositionFP == NULL) {
9497         DisplayError(_("No position has been loaded yet"), 0);
9498         return FALSE;
9499     }
9500     if (positionNumber <= 0) {
9501         DisplayError(_("Can't back up any further"), 0);
9502         return FALSE;
9503     }
9504     return LoadPosition(lastLoadPositionFP, positionNumber,
9505                         lastLoadPositionTitle);
9506 }
9507
9508 /* Load the nth position from the given file */
9509 int
9510 LoadPositionFromFile(filename, n, title)
9511      char *filename;
9512      int n;
9513      char *title;
9514 {
9515     FILE *f;
9516     char buf[MSG_SIZ];
9517
9518     if (strcmp(filename, "-") == 0) {
9519         return LoadPosition(stdin, n, "stdin");
9520     } else {
9521         f = fopen(filename, "rb");
9522         if (f == NULL) {
9523             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9524             DisplayError(buf, errno);
9525             return FALSE;
9526         } else {
9527             return LoadPosition(f, n, title);
9528         }
9529     }
9530 }
9531
9532 /* Load the nth position from the given open file, and close it */
9533 int
9534 LoadPosition(f, positionNumber, title)
9535      FILE *f;
9536      int positionNumber;
9537      char *title;
9538 {
9539     char *p, line[MSG_SIZ];
9540     Board initial_position;
9541     int i, j, fenMode, pn;
9542     
9543     if (gameMode == Training )
9544         SetTrainingModeOff();
9545
9546     if (gameMode != BeginningOfGame) {
9547         Reset(FALSE, TRUE);
9548     }
9549     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9550         fclose(lastLoadPositionFP);
9551     }
9552     if (positionNumber == 0) positionNumber = 1;
9553     lastLoadPositionFP = f;
9554     lastLoadPositionNumber = positionNumber;
9555     strcpy(lastLoadPositionTitle, title);
9556     if (first.pr == NoProc) {
9557       StartChessProgram(&first);
9558       InitChessProgram(&first, FALSE);
9559     }    
9560     pn = positionNumber;
9561     if (positionNumber < 0) {
9562         /* Negative position number means to seek to that byte offset */
9563         if (fseek(f, -positionNumber, 0) == -1) {
9564             DisplayError(_("Can't seek on position file"), 0);
9565             return FALSE;
9566         };
9567         pn = 1;
9568     } else {
9569         if (fseek(f, 0, 0) == -1) {
9570             if (f == lastLoadPositionFP ?
9571                 positionNumber == lastLoadPositionNumber + 1 :
9572                 positionNumber == 1) {
9573                 pn = 1;
9574             } else {
9575                 DisplayError(_("Can't seek on position file"), 0);
9576                 return FALSE;
9577             }
9578         }
9579     }
9580     /* See if this file is FEN or old-style xboard */
9581     if (fgets(line, MSG_SIZ, f) == NULL) {
9582         DisplayError(_("Position not found in file"), 0);
9583         return FALSE;
9584     }
9585     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9586     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9587
9588     if (pn >= 2) {
9589         if (fenMode || line[0] == '#') pn--;
9590         while (pn > 0) {
9591             /* skip positions before number pn */
9592             if (fgets(line, MSG_SIZ, f) == NULL) {
9593                 Reset(TRUE, TRUE);
9594                 DisplayError(_("Position not found in file"), 0);
9595                 return FALSE;
9596             }
9597             if (fenMode || line[0] == '#') pn--;
9598         }
9599     }
9600
9601     if (fenMode) {
9602         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9603             DisplayError(_("Bad FEN position in file"), 0);
9604             return FALSE;
9605         }
9606     } else {
9607         (void) fgets(line, MSG_SIZ, f);
9608         (void) fgets(line, MSG_SIZ, f);
9609     
9610         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9611             (void) fgets(line, MSG_SIZ, f);
9612             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9613                 if (*p == ' ')
9614                   continue;
9615                 initial_position[i][j++] = CharToPiece(*p);
9616             }
9617         }
9618     
9619         blackPlaysFirst = FALSE;
9620         if (!feof(f)) {
9621             (void) fgets(line, MSG_SIZ, f);
9622             if (strncmp(line, "black", strlen("black"))==0)
9623               blackPlaysFirst = TRUE;
9624         }
9625     }
9626     startedFromSetupPosition = TRUE;
9627     
9628     SendToProgram("force\n", &first);
9629     CopyBoard(boards[0], initial_position);
9630     if (blackPlaysFirst) {
9631         currentMove = forwardMostMove = backwardMostMove = 1;
9632         strcpy(moveList[0], "");
9633         strcpy(parseList[0], "");
9634         CopyBoard(boards[1], initial_position);
9635         DisplayMessage("", _("Black to play"));
9636     } else {
9637         currentMove = forwardMostMove = backwardMostMove = 0;
9638         DisplayMessage("", _("White to play"));
9639     }
9640           /* [HGM] copy FEN attributes as well */
9641           {   int i;
9642               initialRulePlies = FENrulePlies;
9643               epStatus[forwardMostMove] = FENepStatus;
9644               for( i=0; i< nrCastlingRights; i++ )
9645                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9646           }
9647     SendBoard(&first, forwardMostMove);
9648     if (appData.debugMode) {
9649 int i, j;
9650   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9651   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9652         fprintf(debugFP, "Load Position\n");
9653     }
9654
9655     if (positionNumber > 1) {
9656         sprintf(line, "%s %d", title, positionNumber);
9657         DisplayTitle(line);
9658     } else {
9659         DisplayTitle(title);
9660     }
9661     gameMode = EditGame;
9662     ModeHighlight();
9663     ResetClocks();
9664     timeRemaining[0][1] = whiteTimeRemaining;
9665     timeRemaining[1][1] = blackTimeRemaining;
9666     DrawPosition(FALSE, boards[currentMove]);
9667    
9668     return TRUE;
9669 }
9670
9671
9672 void
9673 CopyPlayerNameIntoFileName(dest, src)
9674      char **dest, *src;
9675 {
9676     while (*src != NULLCHAR && *src != ',') {
9677         if (*src == ' ') {
9678             *(*dest)++ = '_';
9679             src++;
9680         } else {
9681             *(*dest)++ = *src++;
9682         }
9683     }
9684 }
9685
9686 char *DefaultFileName(ext)
9687      char *ext;
9688 {
9689     static char def[MSG_SIZ];
9690     char *p;
9691
9692     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9693         p = def;
9694         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9695         *p++ = '-';
9696         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9697         *p++ = '.';
9698         strcpy(p, ext);
9699     } else {
9700         def[0] = NULLCHAR;
9701     }
9702     return def;
9703 }
9704
9705 /* Save the current game to the given file */
9706 int
9707 SaveGameToFile(filename, append)
9708      char *filename;
9709      int append;
9710 {
9711     FILE *f;
9712     char buf[MSG_SIZ];
9713
9714     if (strcmp(filename, "-") == 0) {
9715         return SaveGame(stdout, 0, NULL);
9716     } else {
9717         f = fopen(filename, append ? "a" : "w");
9718         if (f == NULL) {
9719             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9720             DisplayError(buf, errno);
9721             return FALSE;
9722         } else {
9723             return SaveGame(f, 0, NULL);
9724         }
9725     }
9726 }
9727
9728 char *
9729 SavePart(str)
9730      char *str;
9731 {
9732     static char buf[MSG_SIZ];
9733     char *p;
9734     
9735     p = strchr(str, ' ');
9736     if (p == NULL) return str;
9737     strncpy(buf, str, p - str);
9738     buf[p - str] = NULLCHAR;
9739     return buf;
9740 }
9741
9742 #define PGN_MAX_LINE 75
9743
9744 #define PGN_SIDE_WHITE  0
9745 #define PGN_SIDE_BLACK  1
9746
9747 /* [AS] */
9748 static int FindFirstMoveOutOfBook( int side )
9749 {
9750     int result = -1;
9751
9752     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9753         int index = backwardMostMove;
9754         int has_book_hit = 0;
9755
9756         if( (index % 2) != side ) {
9757             index++;
9758         }
9759
9760         while( index < forwardMostMove ) {
9761             /* Check to see if engine is in book */
9762             int depth = pvInfoList[index].depth;
9763             int score = pvInfoList[index].score;
9764             int in_book = 0;
9765
9766             if( depth <= 2 ) {
9767                 in_book = 1;
9768             }
9769             else if( score == 0 && depth == 63 ) {
9770                 in_book = 1; /* Zappa */
9771             }
9772             else if( score == 2 && depth == 99 ) {
9773                 in_book = 1; /* Abrok */
9774             }
9775
9776             has_book_hit += in_book;
9777
9778             if( ! in_book ) {
9779                 result = index;
9780
9781                 break;
9782             }
9783
9784             index += 2;
9785         }
9786     }
9787
9788     return result;
9789 }
9790
9791 /* [AS] */
9792 void GetOutOfBookInfo( char * buf )
9793 {
9794     int oob[2];
9795     int i;
9796     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9797
9798     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9799     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9800
9801     *buf = '\0';
9802
9803     if( oob[0] >= 0 || oob[1] >= 0 ) {
9804         for( i=0; i<2; i++ ) {
9805             int idx = oob[i];
9806
9807             if( idx >= 0 ) {
9808                 if( i > 0 && oob[0] >= 0 ) {
9809                     strcat( buf, "   " );
9810                 }
9811
9812                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9813                 sprintf( buf+strlen(buf), "%s%.2f", 
9814                     pvInfoList[idx].score >= 0 ? "+" : "",
9815                     pvInfoList[idx].score / 100.0 );
9816             }
9817         }
9818     }
9819 }
9820
9821 /* Save game in PGN style and close the file */
9822 int
9823 SaveGamePGN(f)
9824      FILE *f;
9825 {
9826     int i, offset, linelen, newblock;
9827     time_t tm;
9828 //    char *movetext;
9829     char numtext[32];
9830     int movelen, numlen, blank;
9831     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9832
9833     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9834     
9835     tm = time((time_t *) NULL);
9836     
9837     PrintPGNTags(f, &gameInfo);
9838     
9839     if (backwardMostMove > 0 || startedFromSetupPosition) {
9840         char *fen = PositionToFEN(backwardMostMove, NULL);
9841         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9842         fprintf(f, "\n{--------------\n");
9843         PrintPosition(f, backwardMostMove);
9844         fprintf(f, "--------------}\n");
9845         free(fen);
9846     }
9847     else {
9848         /* [AS] Out of book annotation */
9849         if( appData.saveOutOfBookInfo ) {
9850             char buf[64];
9851
9852             GetOutOfBookInfo( buf );
9853
9854             if( buf[0] != '\0' ) {
9855                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9856             }
9857         }
9858
9859         fprintf(f, "\n");
9860     }
9861
9862     i = backwardMostMove;
9863     linelen = 0;
9864     newblock = TRUE;
9865
9866     while (i < forwardMostMove) {
9867         /* Print comments preceding this move */
9868         if (commentList[i] != NULL) {
9869             if (linelen > 0) fprintf(f, "\n");
9870             fprintf(f, "{\n%s}\n", commentList[i]);
9871             linelen = 0;
9872             newblock = TRUE;
9873         }
9874
9875         /* Format move number */
9876         if ((i % 2) == 0) {
9877             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9878         } else {
9879             if (newblock) {
9880                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9881             } else {
9882                 numtext[0] = NULLCHAR;
9883             }
9884         }
9885         numlen = strlen(numtext);
9886         newblock = FALSE;
9887
9888         /* Print move number */
9889         blank = linelen > 0 && numlen > 0;
9890         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9891             fprintf(f, "\n");
9892             linelen = 0;
9893             blank = 0;
9894         }
9895         if (blank) {
9896             fprintf(f, " ");
9897             linelen++;
9898         }
9899         fprintf(f, "%s", numtext);
9900         linelen += numlen;
9901
9902         /* Get move */
9903         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9904         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9905
9906         /* Print move */
9907         blank = linelen > 0 && movelen > 0;
9908         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9909             fprintf(f, "\n");
9910             linelen = 0;
9911             blank = 0;
9912         }
9913         if (blank) {
9914             fprintf(f, " ");
9915             linelen++;
9916         }
9917         fprintf(f, "%s", move_buffer);
9918         linelen += movelen;
9919
9920         /* [AS] Add PV info if present */
9921         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9922             /* [HGM] add time */
9923             char buf[MSG_SIZ]; int seconds = 0;
9924
9925             if(i >= backwardMostMove) {
9926                 if(WhiteOnMove(i))
9927                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9928                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9929                 else
9930                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9931                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9932             }
9933             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9934
9935             if( seconds <= 0) buf[0] = 0; else
9936             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9937                 seconds = (seconds + 4)/10; // round to full seconds
9938                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9939                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9940             }
9941
9942             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9943                 pvInfoList[i].score >= 0 ? "+" : "",
9944                 pvInfoList[i].score / 100.0,
9945                 pvInfoList[i].depth,
9946                 buf );
9947
9948             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9949
9950             /* Print score/depth */
9951             blank = linelen > 0 && movelen > 0;
9952             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9953                 fprintf(f, "\n");
9954                 linelen = 0;
9955                 blank = 0;
9956             }
9957             if (blank) {
9958                 fprintf(f, " ");
9959                 linelen++;
9960             }
9961             fprintf(f, "%s", move_buffer);
9962             linelen += movelen;
9963         }
9964
9965         i++;
9966     }
9967     
9968     /* Start a new line */
9969     if (linelen > 0) fprintf(f, "\n");
9970
9971     /* Print comments after last move */
9972     if (commentList[i] != NULL) {
9973         fprintf(f, "{\n%s}\n", commentList[i]);
9974     }
9975
9976     /* Print result */
9977     if (gameInfo.resultDetails != NULL &&
9978         gameInfo.resultDetails[0] != NULLCHAR) {
9979         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9980                 PGNResult(gameInfo.result));
9981     } else {
9982         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9983     }
9984
9985     fclose(f);
9986     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9987     return TRUE;
9988 }
9989
9990 /* Save game in old style and close the file */
9991 int
9992 SaveGameOldStyle(f)
9993      FILE *f;
9994 {
9995     int i, offset;
9996     time_t tm;
9997     
9998     tm = time((time_t *) NULL);
9999     
10000     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10001     PrintOpponents(f);
10002     
10003     if (backwardMostMove > 0 || startedFromSetupPosition) {
10004         fprintf(f, "\n[--------------\n");
10005         PrintPosition(f, backwardMostMove);
10006         fprintf(f, "--------------]\n");
10007     } else {
10008         fprintf(f, "\n");
10009     }
10010
10011     i = backwardMostMove;
10012     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10013
10014     while (i < forwardMostMove) {
10015         if (commentList[i] != NULL) {
10016             fprintf(f, "[%s]\n", commentList[i]);
10017         }
10018
10019         if ((i % 2) == 1) {
10020             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10021             i++;
10022         } else {
10023             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10024             i++;
10025             if (commentList[i] != NULL) {
10026                 fprintf(f, "\n");
10027                 continue;
10028             }
10029             if (i >= forwardMostMove) {
10030                 fprintf(f, "\n");
10031                 break;
10032             }
10033             fprintf(f, "%s\n", parseList[i]);
10034             i++;
10035         }
10036     }
10037     
10038     if (commentList[i] != NULL) {
10039         fprintf(f, "[%s]\n", commentList[i]);
10040     }
10041
10042     /* This isn't really the old style, but it's close enough */
10043     if (gameInfo.resultDetails != NULL &&
10044         gameInfo.resultDetails[0] != NULLCHAR) {
10045         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10046                 gameInfo.resultDetails);
10047     } else {
10048         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10049     }
10050
10051     fclose(f);
10052     return TRUE;
10053 }
10054
10055 /* Save the current game to open file f and close the file */
10056 int
10057 SaveGame(f, dummy, dummy2)
10058      FILE *f;
10059      int dummy;
10060      char *dummy2;
10061 {
10062     if (gameMode == EditPosition) EditPositionDone();
10063     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10064     if (appData.oldSaveStyle)
10065       return SaveGameOldStyle(f);
10066     else
10067       return SaveGamePGN(f);
10068 }
10069
10070 /* Save the current position to the given file */
10071 int
10072 SavePositionToFile(filename)
10073      char *filename;
10074 {
10075     FILE *f;
10076     char buf[MSG_SIZ];
10077
10078     if (strcmp(filename, "-") == 0) {
10079         return SavePosition(stdout, 0, NULL);
10080     } else {
10081         f = fopen(filename, "a");
10082         if (f == NULL) {
10083             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10084             DisplayError(buf, errno);
10085             return FALSE;
10086         } else {
10087             SavePosition(f, 0, NULL);
10088             return TRUE;
10089         }
10090     }
10091 }
10092
10093 /* Save the current position to the given open file and close the file */
10094 int
10095 SavePosition(f, dummy, dummy2)
10096      FILE *f;
10097      int dummy;
10098      char *dummy2;
10099 {
10100     time_t tm;
10101     char *fen;
10102     
10103     if (appData.oldSaveStyle) {
10104         tm = time((time_t *) NULL);
10105     
10106         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10107         PrintOpponents(f);
10108         fprintf(f, "[--------------\n");
10109         PrintPosition(f, currentMove);
10110         fprintf(f, "--------------]\n");
10111     } else {
10112         fen = PositionToFEN(currentMove, NULL);
10113         fprintf(f, "%s\n", fen);
10114         free(fen);
10115     }
10116     fclose(f);
10117     return TRUE;
10118 }
10119
10120 void
10121 ReloadCmailMsgEvent(unregister)
10122      int unregister;
10123 {
10124 #if !WIN32
10125     static char *inFilename = NULL;
10126     static char *outFilename;
10127     int i;
10128     struct stat inbuf, outbuf;
10129     int status;
10130     
10131     /* Any registered moves are unregistered if unregister is set, */
10132     /* i.e. invoked by the signal handler */
10133     if (unregister) {
10134         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10135             cmailMoveRegistered[i] = FALSE;
10136             if (cmailCommentList[i] != NULL) {
10137                 free(cmailCommentList[i]);
10138                 cmailCommentList[i] = NULL;
10139             }
10140         }
10141         nCmailMovesRegistered = 0;
10142     }
10143
10144     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10145         cmailResult[i] = CMAIL_NOT_RESULT;
10146     }
10147     nCmailResults = 0;
10148
10149     if (inFilename == NULL) {
10150         /* Because the filenames are static they only get malloced once  */
10151         /* and they never get freed                                      */
10152         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10153         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10154
10155         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10156         sprintf(outFilename, "%s.out", appData.cmailGameName);
10157     }
10158     
10159     status = stat(outFilename, &outbuf);
10160     if (status < 0) {
10161         cmailMailedMove = FALSE;
10162     } else {
10163         status = stat(inFilename, &inbuf);
10164         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10165     }
10166     
10167     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10168        counts the games, notes how each one terminated, etc.
10169        
10170        It would be nice to remove this kludge and instead gather all
10171        the information while building the game list.  (And to keep it
10172        in the game list nodes instead of having a bunch of fixed-size
10173        parallel arrays.)  Note this will require getting each game's
10174        termination from the PGN tags, as the game list builder does
10175        not process the game moves.  --mann
10176        */
10177     cmailMsgLoaded = TRUE;
10178     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10179     
10180     /* Load first game in the file or popup game menu */
10181     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10182
10183 #endif /* !WIN32 */
10184     return;
10185 }
10186
10187 int
10188 RegisterMove()
10189 {
10190     FILE *f;
10191     char string[MSG_SIZ];
10192
10193     if (   cmailMailedMove
10194         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10195         return TRUE;            /* Allow free viewing  */
10196     }
10197
10198     /* Unregister move to ensure that we don't leave RegisterMove        */
10199     /* with the move registered when the conditions for registering no   */
10200     /* longer hold                                                       */
10201     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10202         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10203         nCmailMovesRegistered --;
10204
10205         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10206           {
10207               free(cmailCommentList[lastLoadGameNumber - 1]);
10208               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10209           }
10210     }
10211
10212     if (cmailOldMove == -1) {
10213         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10214         return FALSE;
10215     }
10216
10217     if (currentMove > cmailOldMove + 1) {
10218         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10219         return FALSE;
10220     }
10221
10222     if (currentMove < cmailOldMove) {
10223         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10224         return FALSE;
10225     }
10226
10227     if (forwardMostMove > currentMove) {
10228         /* Silently truncate extra moves */
10229         TruncateGame();
10230     }
10231
10232     if (   (currentMove == cmailOldMove + 1)
10233         || (   (currentMove == cmailOldMove)
10234             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10235                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10236         if (gameInfo.result != GameUnfinished) {
10237             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10238         }
10239
10240         if (commentList[currentMove] != NULL) {
10241             cmailCommentList[lastLoadGameNumber - 1]
10242               = StrSave(commentList[currentMove]);
10243         }
10244         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10245
10246         if (appData.debugMode)
10247           fprintf(debugFP, "Saving %s for game %d\n",
10248                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10249
10250         sprintf(string,
10251                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10252         
10253         f = fopen(string, "w");
10254         if (appData.oldSaveStyle) {
10255             SaveGameOldStyle(f); /* also closes the file */
10256             
10257             sprintf(string, "%s.pos.out", appData.cmailGameName);
10258             f = fopen(string, "w");
10259             SavePosition(f, 0, NULL); /* also closes the file */
10260         } else {
10261             fprintf(f, "{--------------\n");
10262             PrintPosition(f, currentMove);
10263             fprintf(f, "--------------}\n\n");
10264             
10265             SaveGame(f, 0, NULL); /* also closes the file*/
10266         }
10267         
10268         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10269         nCmailMovesRegistered ++;
10270     } else if (nCmailGames == 1) {
10271         DisplayError(_("You have not made a move yet"), 0);
10272         return FALSE;
10273     }
10274
10275     return TRUE;
10276 }
10277
10278 void
10279 MailMoveEvent()
10280 {
10281 #if !WIN32
10282     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10283     FILE *commandOutput;
10284     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10285     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10286     int nBuffers;
10287     int i;
10288     int archived;
10289     char *arcDir;
10290
10291     if (! cmailMsgLoaded) {
10292         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10293         return;
10294     }
10295
10296     if (nCmailGames == nCmailResults) {
10297         DisplayError(_("No unfinished games"), 0);
10298         return;
10299     }
10300
10301 #if CMAIL_PROHIBIT_REMAIL
10302     if (cmailMailedMove) {
10303         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);
10304         DisplayError(msg, 0);
10305         return;
10306     }
10307 #endif
10308
10309     if (! (cmailMailedMove || RegisterMove())) return;
10310     
10311     if (   cmailMailedMove
10312         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10313         sprintf(string, partCommandString,
10314                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10315         commandOutput = popen(string, "r");
10316
10317         if (commandOutput == NULL) {
10318             DisplayError(_("Failed to invoke cmail"), 0);
10319         } else {
10320             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10321                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10322             }
10323             if (nBuffers > 1) {
10324                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10325                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10326                 nBytes = MSG_SIZ - 1;
10327             } else {
10328                 (void) memcpy(msg, buffer, nBytes);
10329             }
10330             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10331
10332             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10333                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10334
10335                 archived = TRUE;
10336                 for (i = 0; i < nCmailGames; i ++) {
10337                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10338                         archived = FALSE;
10339                     }
10340                 }
10341                 if (   archived
10342                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10343                         != NULL)) {
10344                     sprintf(buffer, "%s/%s.%s.archive",
10345                             arcDir,
10346                             appData.cmailGameName,
10347                             gameInfo.date);
10348                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10349                     cmailMsgLoaded = FALSE;
10350                 }
10351             }
10352
10353             DisplayInformation(msg);
10354             pclose(commandOutput);
10355         }
10356     } else {
10357         if ((*cmailMsg) != '\0') {
10358             DisplayInformation(cmailMsg);
10359         }
10360     }
10361
10362     return;
10363 #endif /* !WIN32 */
10364 }
10365
10366 char *
10367 CmailMsg()
10368 {
10369 #if WIN32
10370     return NULL;
10371 #else
10372     int  prependComma = 0;
10373     char number[5];
10374     char string[MSG_SIZ];       /* Space for game-list */
10375     int  i;
10376     
10377     if (!cmailMsgLoaded) return "";
10378
10379     if (cmailMailedMove) {
10380         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10381     } else {
10382         /* Create a list of games left */
10383         sprintf(string, "[");
10384         for (i = 0; i < nCmailGames; i ++) {
10385             if (! (   cmailMoveRegistered[i]
10386                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10387                 if (prependComma) {
10388                     sprintf(number, ",%d", i + 1);
10389                 } else {
10390                     sprintf(number, "%d", i + 1);
10391                     prependComma = 1;
10392                 }
10393                 
10394                 strcat(string, number);
10395             }
10396         }
10397         strcat(string, "]");
10398
10399         if (nCmailMovesRegistered + nCmailResults == 0) {
10400             switch (nCmailGames) {
10401               case 1:
10402                 sprintf(cmailMsg,
10403                         _("Still need to make move for game\n"));
10404                 break;
10405                 
10406               case 2:
10407                 sprintf(cmailMsg,
10408                         _("Still need to make moves for both games\n"));
10409                 break;
10410                 
10411               default:
10412                 sprintf(cmailMsg,
10413                         _("Still need to make moves for all %d games\n"),
10414                         nCmailGames);
10415                 break;
10416             }
10417         } else {
10418             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10419               case 1:
10420                 sprintf(cmailMsg,
10421                         _("Still need to make a move for game %s\n"),
10422                         string);
10423                 break;
10424                 
10425               case 0:
10426                 if (nCmailResults == nCmailGames) {
10427                     sprintf(cmailMsg, _("No unfinished games\n"));
10428                 } else {
10429                     sprintf(cmailMsg, _("Ready to send mail\n"));
10430                 }
10431                 break;
10432                 
10433               default:
10434                 sprintf(cmailMsg,
10435                         _("Still need to make moves for games %s\n"),
10436                         string);
10437             }
10438         }
10439     }
10440     return cmailMsg;
10441 #endif /* WIN32 */
10442 }
10443
10444 void
10445 ResetGameEvent()
10446 {
10447     if (gameMode == Training)
10448       SetTrainingModeOff();
10449
10450     Reset(TRUE, TRUE);
10451     cmailMsgLoaded = FALSE;
10452     if (appData.icsActive) {
10453       SendToICS(ics_prefix);
10454       SendToICS("refresh\n");
10455     }
10456 }
10457
10458 void
10459 ExitEvent(status)
10460      int status;
10461 {
10462     exiting++;
10463     if (exiting > 2) {
10464       /* Give up on clean exit */
10465       exit(status);
10466     }
10467     if (exiting > 1) {
10468       /* Keep trying for clean exit */
10469       return;
10470     }
10471
10472     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10473
10474     if (telnetISR != NULL) {
10475       RemoveInputSource(telnetISR);
10476     }
10477     if (icsPR != NoProc) {
10478       DestroyChildProcess(icsPR, TRUE);
10479     }
10480
10481     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10482     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10483
10484     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10485     /* make sure this other one finishes before killing it!                  */
10486     if(endingGame) { int count = 0;
10487         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10488         while(endingGame && count++ < 10) DoSleep(1);
10489         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10490     }
10491
10492     /* Kill off chess programs */
10493     if (first.pr != NoProc) {
10494         ExitAnalyzeMode();
10495         
10496         DoSleep( appData.delayBeforeQuit );
10497         SendToProgram("quit\n", &first);
10498         DoSleep( appData.delayAfterQuit );
10499         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10500     }
10501     if (second.pr != NoProc) {
10502         DoSleep( appData.delayBeforeQuit );
10503         SendToProgram("quit\n", &second);
10504         DoSleep( appData.delayAfterQuit );
10505         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10506     }
10507     if (first.isr != NULL) {
10508         RemoveInputSource(first.isr);
10509     }
10510     if (second.isr != NULL) {
10511         RemoveInputSource(second.isr);
10512     }
10513
10514     ShutDownFrontEnd();
10515     exit(status);
10516 }
10517
10518 void
10519 PauseEvent()
10520 {
10521     if (appData.debugMode)
10522         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10523     if (pausing) {
10524         pausing = FALSE;
10525         ModeHighlight();
10526         if (gameMode == MachinePlaysWhite ||
10527             gameMode == MachinePlaysBlack) {
10528             StartClocks();
10529         } else {
10530             DisplayBothClocks();
10531         }
10532         if (gameMode == PlayFromGameFile) {
10533             if (appData.timeDelay >= 0) 
10534                 AutoPlayGameLoop();
10535         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10536             Reset(FALSE, TRUE);
10537             SendToICS(ics_prefix);
10538             SendToICS("refresh\n");
10539         } else if (currentMove < forwardMostMove) {
10540             ForwardInner(forwardMostMove);
10541         }
10542         pauseExamInvalid = FALSE;
10543     } else {
10544         switch (gameMode) {
10545           default:
10546             return;
10547           case IcsExamining:
10548             pauseExamForwardMostMove = forwardMostMove;
10549             pauseExamInvalid = FALSE;
10550             /* fall through */
10551           case IcsObserving:
10552           case IcsPlayingWhite:
10553           case IcsPlayingBlack:
10554             pausing = TRUE;
10555             ModeHighlight();
10556             return;
10557           case PlayFromGameFile:
10558             (void) StopLoadGameTimer();
10559             pausing = TRUE;
10560             ModeHighlight();
10561             break;
10562           case BeginningOfGame:
10563             if (appData.icsActive) return;
10564             /* else fall through */
10565           case MachinePlaysWhite:
10566           case MachinePlaysBlack:
10567           case TwoMachinesPlay:
10568             if (forwardMostMove == 0)
10569               return;           /* don't pause if no one has moved */
10570             if ((gameMode == MachinePlaysWhite &&
10571                  !WhiteOnMove(forwardMostMove)) ||
10572                 (gameMode == MachinePlaysBlack &&
10573                  WhiteOnMove(forwardMostMove))) {
10574                 StopClocks();
10575             }
10576             pausing = TRUE;
10577             ModeHighlight();
10578             break;
10579         }
10580     }
10581 }
10582
10583 void
10584 EditCommentEvent()
10585 {
10586     char title[MSG_SIZ];
10587
10588     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10589         strcpy(title, _("Edit comment"));
10590     } else {
10591         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10592                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10593                 parseList[currentMove - 1]);
10594     }
10595
10596     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10597 }
10598
10599
10600 void
10601 EditTagsEvent()
10602 {
10603     char *tags = PGNTags(&gameInfo);
10604     EditTagsPopUp(tags);
10605     free(tags);
10606 }
10607
10608 void
10609 AnalyzeModeEvent()
10610 {
10611     if (appData.noChessProgram || gameMode == AnalyzeMode)
10612       return;
10613
10614     if (gameMode != AnalyzeFile) {
10615         if (!appData.icsEngineAnalyze) {
10616                EditGameEvent();
10617                if (gameMode != EditGame) return;
10618         }
10619         ResurrectChessProgram();
10620         SendToProgram("analyze\n", &first);
10621         first.analyzing = TRUE;
10622         /*first.maybeThinking = TRUE;*/
10623         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10624         EngineOutputPopUp();
10625     }
10626     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10627     pausing = FALSE;
10628     ModeHighlight();
10629     SetGameInfo();
10630
10631     StartAnalysisClock();
10632     GetTimeMark(&lastNodeCountTime);
10633     lastNodeCount = 0;
10634 }
10635
10636 void
10637 AnalyzeFileEvent()
10638 {
10639     if (appData.noChessProgram || gameMode == AnalyzeFile)
10640       return;
10641
10642     if (gameMode != AnalyzeMode) {
10643         EditGameEvent();
10644         if (gameMode != EditGame) return;
10645         ResurrectChessProgram();
10646         SendToProgram("analyze\n", &first);
10647         first.analyzing = TRUE;
10648         /*first.maybeThinking = TRUE;*/
10649         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10650         EngineOutputPopUp();
10651     }
10652     gameMode = AnalyzeFile;
10653     pausing = FALSE;
10654     ModeHighlight();
10655     SetGameInfo();
10656
10657     StartAnalysisClock();
10658     GetTimeMark(&lastNodeCountTime);
10659     lastNodeCount = 0;
10660 }
10661
10662 void
10663 MachineWhiteEvent()
10664 {
10665     char buf[MSG_SIZ];
10666     char *bookHit = NULL;
10667
10668     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10669       return;
10670
10671
10672     if (gameMode == PlayFromGameFile || 
10673         gameMode == TwoMachinesPlay  || 
10674         gameMode == Training         || 
10675         gameMode == AnalyzeMode      || 
10676         gameMode == EndOfGame)
10677         EditGameEvent();
10678
10679     if (gameMode == EditPosition) 
10680         EditPositionDone();
10681
10682     if (!WhiteOnMove(currentMove)) {
10683         DisplayError(_("It is not White's turn"), 0);
10684         return;
10685     }
10686   
10687     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10688       ExitAnalyzeMode();
10689
10690     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10691         gameMode == AnalyzeFile)
10692         TruncateGame();
10693
10694     ResurrectChessProgram();    /* in case it isn't running */
10695     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10696         gameMode = MachinePlaysWhite;
10697         ResetClocks();
10698     } else
10699     gameMode = MachinePlaysWhite;
10700     pausing = FALSE;
10701     ModeHighlight();
10702     SetGameInfo();
10703     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10704     DisplayTitle(buf);
10705     if (first.sendName) {
10706       sprintf(buf, "name %s\n", gameInfo.black);
10707       SendToProgram(buf, &first);
10708     }
10709     if (first.sendTime) {
10710       if (first.useColors) {
10711         SendToProgram("black\n", &first); /*gnu kludge*/
10712       }
10713       SendTimeRemaining(&first, TRUE);
10714     }
10715     if (first.useColors) {
10716       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10717     }
10718     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10719     SetMachineThinkingEnables();
10720     first.maybeThinking = TRUE;
10721     StartClocks();
10722     firstMove = FALSE;
10723
10724     if (appData.autoFlipView && !flipView) {
10725       flipView = !flipView;
10726       DrawPosition(FALSE, NULL);
10727       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10728     }
10729
10730     if(bookHit) { // [HGM] book: simulate book reply
10731         static char bookMove[MSG_SIZ]; // a bit generous?
10732
10733         programStats.nodes = programStats.depth = programStats.time = 
10734         programStats.score = programStats.got_only_move = 0;
10735         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10736
10737         strcpy(bookMove, "move ");
10738         strcat(bookMove, bookHit);
10739         HandleMachineMove(bookMove, &first);
10740     }
10741 }
10742
10743 void
10744 MachineBlackEvent()
10745 {
10746     char buf[MSG_SIZ];
10747    char *bookHit = NULL;
10748
10749     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10750         return;
10751
10752
10753     if (gameMode == PlayFromGameFile || 
10754         gameMode == TwoMachinesPlay  || 
10755         gameMode == Training         || 
10756         gameMode == AnalyzeMode      || 
10757         gameMode == EndOfGame)
10758         EditGameEvent();
10759
10760     if (gameMode == EditPosition) 
10761         EditPositionDone();
10762
10763     if (WhiteOnMove(currentMove)) {
10764         DisplayError(_("It is not Black's turn"), 0);
10765         return;
10766     }
10767     
10768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10769       ExitAnalyzeMode();
10770
10771     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10772         gameMode == AnalyzeFile)
10773         TruncateGame();
10774
10775     ResurrectChessProgram();    /* in case it isn't running */
10776     gameMode = MachinePlaysBlack;
10777     pausing = FALSE;
10778     ModeHighlight();
10779     SetGameInfo();
10780     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10781     DisplayTitle(buf);
10782     if (first.sendName) {
10783       sprintf(buf, "name %s\n", gameInfo.white);
10784       SendToProgram(buf, &first);
10785     }
10786     if (first.sendTime) {
10787       if (first.useColors) {
10788         SendToProgram("white\n", &first); /*gnu kludge*/
10789       }
10790       SendTimeRemaining(&first, FALSE);
10791     }
10792     if (first.useColors) {
10793       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10794     }
10795     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10796     SetMachineThinkingEnables();
10797     first.maybeThinking = TRUE;
10798     StartClocks();
10799
10800     if (appData.autoFlipView && flipView) {
10801       flipView = !flipView;
10802       DrawPosition(FALSE, NULL);
10803       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10804     }
10805     if(bookHit) { // [HGM] book: simulate book reply
10806         static char bookMove[MSG_SIZ]; // a bit generous?
10807
10808         programStats.nodes = programStats.depth = programStats.time = 
10809         programStats.score = programStats.got_only_move = 0;
10810         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10811
10812         strcpy(bookMove, "move ");
10813         strcat(bookMove, bookHit);
10814         HandleMachineMove(bookMove, &first);
10815     }
10816 }
10817
10818
10819 void
10820 DisplayTwoMachinesTitle()
10821 {
10822     char buf[MSG_SIZ];
10823     if (appData.matchGames > 0) {
10824         if (first.twoMachinesColor[0] == 'w') {
10825             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10826                     gameInfo.white, gameInfo.black,
10827                     first.matchWins, second.matchWins,
10828                     matchGame - 1 - (first.matchWins + second.matchWins));
10829         } else {
10830             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10831                     gameInfo.white, gameInfo.black,
10832                     second.matchWins, first.matchWins,
10833                     matchGame - 1 - (first.matchWins + second.matchWins));
10834         }
10835     } else {
10836         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10837     }
10838     DisplayTitle(buf);
10839 }
10840
10841 void
10842 TwoMachinesEvent P((void))
10843 {
10844     int i;
10845     char buf[MSG_SIZ];
10846     ChessProgramState *onmove;
10847     char *bookHit = NULL;
10848     
10849     if (appData.noChessProgram) return;
10850
10851     switch (gameMode) {
10852       case TwoMachinesPlay:
10853         return;
10854       case MachinePlaysWhite:
10855       case MachinePlaysBlack:
10856         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10857             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10858             return;
10859         }
10860         /* fall through */
10861       case BeginningOfGame:
10862       case PlayFromGameFile:
10863       case EndOfGame:
10864         EditGameEvent();
10865         if (gameMode != EditGame) return;
10866         break;
10867       case EditPosition:
10868         EditPositionDone();
10869         break;
10870       case AnalyzeMode:
10871       case AnalyzeFile:
10872         ExitAnalyzeMode();
10873         break;
10874       case EditGame:
10875       default:
10876         break;
10877     }
10878
10879     forwardMostMove = currentMove;
10880     ResurrectChessProgram();    /* in case first program isn't running */
10881
10882     if (second.pr == NULL) {
10883         StartChessProgram(&second);
10884         if (second.protocolVersion == 1) {
10885           TwoMachinesEventIfReady();
10886         } else {
10887           /* kludge: allow timeout for initial "feature" command */
10888           FreezeUI();
10889           DisplayMessage("", _("Starting second chess program"));
10890           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10891         }
10892         return;
10893     }
10894     DisplayMessage("", "");
10895     InitChessProgram(&second, FALSE);
10896     SendToProgram("force\n", &second);
10897     if (startedFromSetupPosition) {
10898         SendBoard(&second, backwardMostMove);
10899     if (appData.debugMode) {
10900         fprintf(debugFP, "Two Machines\n");
10901     }
10902     }
10903     for (i = backwardMostMove; i < forwardMostMove; i++) {
10904         SendMoveToProgram(i, &second);
10905     }
10906
10907     gameMode = TwoMachinesPlay;
10908     pausing = FALSE;
10909     ModeHighlight();
10910     SetGameInfo();
10911     DisplayTwoMachinesTitle();
10912     firstMove = TRUE;
10913     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10914         onmove = &first;
10915     } else {
10916         onmove = &second;
10917     }
10918
10919     SendToProgram(first.computerString, &first);
10920     if (first.sendName) {
10921       sprintf(buf, "name %s\n", second.tidy);
10922       SendToProgram(buf, &first);
10923     }
10924     SendToProgram(second.computerString, &second);
10925     if (second.sendName) {
10926       sprintf(buf, "name %s\n", first.tidy);
10927       SendToProgram(buf, &second);
10928     }
10929
10930     ResetClocks();
10931     if (!first.sendTime || !second.sendTime) {
10932         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10933         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10934     }
10935     if (onmove->sendTime) {
10936       if (onmove->useColors) {
10937         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10938       }
10939       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10940     }
10941     if (onmove->useColors) {
10942       SendToProgram(onmove->twoMachinesColor, onmove);
10943     }
10944     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10945 //    SendToProgram("go\n", onmove);
10946     onmove->maybeThinking = TRUE;
10947     SetMachineThinkingEnables();
10948
10949     StartClocks();
10950
10951     if(bookHit) { // [HGM] book: simulate book reply
10952         static char bookMove[MSG_SIZ]; // a bit generous?
10953
10954         programStats.nodes = programStats.depth = programStats.time = 
10955         programStats.score = programStats.got_only_move = 0;
10956         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10957
10958         strcpy(bookMove, "move ");
10959         strcat(bookMove, bookHit);
10960         savedMessage = bookMove; // args for deferred call
10961         savedState = onmove;
10962         ScheduleDelayedEvent(DeferredBookMove, 1);
10963     }
10964 }
10965
10966 void
10967 TrainingEvent()
10968 {
10969     if (gameMode == Training) {
10970       SetTrainingModeOff();
10971       gameMode = PlayFromGameFile;
10972       DisplayMessage("", _("Training mode off"));
10973     } else {
10974       gameMode = Training;
10975       animateTraining = appData.animate;
10976
10977       /* make sure we are not already at the end of the game */
10978       if (currentMove < forwardMostMove) {
10979         SetTrainingModeOn();
10980         DisplayMessage("", _("Training mode on"));
10981       } else {
10982         gameMode = PlayFromGameFile;
10983         DisplayError(_("Already at end of game"), 0);
10984       }
10985     }
10986     ModeHighlight();
10987 }
10988
10989 void
10990 IcsClientEvent()
10991 {
10992     if (!appData.icsActive) return;
10993     switch (gameMode) {
10994       case IcsPlayingWhite:
10995       case IcsPlayingBlack:
10996       case IcsObserving:
10997       case IcsIdle:
10998       case BeginningOfGame:
10999       case IcsExamining:
11000         return;
11001
11002       case EditGame:
11003         break;
11004
11005       case EditPosition:
11006         EditPositionDone();
11007         break;
11008
11009       case AnalyzeMode:
11010       case AnalyzeFile:
11011         ExitAnalyzeMode();
11012         break;
11013         
11014       default:
11015         EditGameEvent();
11016         break;
11017     }
11018
11019     gameMode = IcsIdle;
11020     ModeHighlight();
11021     return;
11022 }
11023
11024
11025 void
11026 EditGameEvent()
11027 {
11028     int i;
11029
11030     switch (gameMode) {
11031       case Training:
11032         SetTrainingModeOff();
11033         break;
11034       case MachinePlaysWhite:
11035       case MachinePlaysBlack:
11036       case BeginningOfGame:
11037         SendToProgram("force\n", &first);
11038         SetUserThinkingEnables();
11039         break;
11040       case PlayFromGameFile:
11041         (void) StopLoadGameTimer();
11042         if (gameFileFP != NULL) {
11043             gameFileFP = NULL;
11044         }
11045         break;
11046       case EditPosition:
11047         EditPositionDone();
11048         break;
11049       case AnalyzeMode:
11050       case AnalyzeFile:
11051         ExitAnalyzeMode();
11052         SendToProgram("force\n", &first);
11053         break;
11054       case TwoMachinesPlay:
11055         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11056         ResurrectChessProgram();
11057         SetUserThinkingEnables();
11058         break;
11059       case EndOfGame:
11060         ResurrectChessProgram();
11061         break;
11062       case IcsPlayingBlack:
11063       case IcsPlayingWhite:
11064         DisplayError(_("Warning: You are still playing a game"), 0);
11065         break;
11066       case IcsObserving:
11067         DisplayError(_("Warning: You are still observing a game"), 0);
11068         break;
11069       case IcsExamining:
11070         DisplayError(_("Warning: You are still examining a game"), 0);
11071         break;
11072       case IcsIdle:
11073         break;
11074       case EditGame:
11075       default:
11076         return;
11077     }
11078     
11079     pausing = FALSE;
11080     StopClocks();
11081     first.offeredDraw = second.offeredDraw = 0;
11082
11083     if (gameMode == PlayFromGameFile) {
11084         whiteTimeRemaining = timeRemaining[0][currentMove];
11085         blackTimeRemaining = timeRemaining[1][currentMove];
11086         DisplayTitle("");
11087     }
11088
11089     if (gameMode == MachinePlaysWhite ||
11090         gameMode == MachinePlaysBlack ||
11091         gameMode == TwoMachinesPlay ||
11092         gameMode == EndOfGame) {
11093         i = forwardMostMove;
11094         while (i > currentMove) {
11095             SendToProgram("undo\n", &first);
11096             i--;
11097         }
11098         whiteTimeRemaining = timeRemaining[0][currentMove];
11099         blackTimeRemaining = timeRemaining[1][currentMove];
11100         DisplayBothClocks();
11101         if (whiteFlag || blackFlag) {
11102             whiteFlag = blackFlag = 0;
11103         }
11104         DisplayTitle("");
11105     }           
11106     
11107     gameMode = EditGame;
11108     ModeHighlight();
11109     SetGameInfo();
11110 }
11111
11112
11113 void
11114 EditPositionEvent()
11115 {
11116     if (gameMode == EditPosition) {
11117         EditGameEvent();
11118         return;
11119     }
11120     
11121     EditGameEvent();
11122     if (gameMode != EditGame) return;
11123     
11124     gameMode = EditPosition;
11125     ModeHighlight();
11126     SetGameInfo();
11127     if (currentMove > 0)
11128       CopyBoard(boards[0], boards[currentMove]);
11129     
11130     blackPlaysFirst = !WhiteOnMove(currentMove);
11131     ResetClocks();
11132     currentMove = forwardMostMove = backwardMostMove = 0;
11133     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11134     DisplayMove(-1);
11135 }
11136
11137 void
11138 ExitAnalyzeMode()
11139 {
11140     /* [DM] icsEngineAnalyze - possible call from other functions */
11141     if (appData.icsEngineAnalyze) {
11142         appData.icsEngineAnalyze = FALSE;
11143
11144         DisplayMessage("",_("Close ICS engine analyze..."));
11145     }
11146     if (first.analysisSupport && first.analyzing) {
11147       SendToProgram("exit\n", &first);
11148       first.analyzing = FALSE;
11149     }
11150     thinkOutput[0] = NULLCHAR;
11151 }
11152
11153 void
11154 EditPositionDone()
11155 {
11156     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11157
11158     startedFromSetupPosition = TRUE;
11159     InitChessProgram(&first, FALSE);
11160     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11161     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11162         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11163         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11164     } else castlingRights[0][2] = -1;
11165     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11166         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11167         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11168     } else castlingRights[0][5] = -1;
11169     SendToProgram("force\n", &first);
11170     if (blackPlaysFirst) {
11171         strcpy(moveList[0], "");
11172         strcpy(parseList[0], "");
11173         currentMove = forwardMostMove = backwardMostMove = 1;
11174         CopyBoard(boards[1], boards[0]);
11175         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11176         { int i;
11177           epStatus[1] = epStatus[0];
11178           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11179         }
11180     } else {
11181         currentMove = forwardMostMove = backwardMostMove = 0;
11182     }
11183     SendBoard(&first, forwardMostMove);
11184     if (appData.debugMode) {
11185         fprintf(debugFP, "EditPosDone\n");
11186     }
11187     DisplayTitle("");
11188     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11189     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11190     gameMode = EditGame;
11191     ModeHighlight();
11192     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11193     ClearHighlights(); /* [AS] */
11194 }
11195
11196 /* Pause for `ms' milliseconds */
11197 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11198 void
11199 TimeDelay(ms)
11200      long ms;
11201 {
11202     TimeMark m1, m2;
11203
11204     GetTimeMark(&m1);
11205     do {
11206         GetTimeMark(&m2);
11207     } while (SubtractTimeMarks(&m2, &m1) < ms);
11208 }
11209
11210 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11211 void
11212 SendMultiLineToICS(buf)
11213      char *buf;
11214 {
11215     char temp[MSG_SIZ+1], *p;
11216     int len;
11217
11218     len = strlen(buf);
11219     if (len > MSG_SIZ)
11220       len = MSG_SIZ;
11221   
11222     strncpy(temp, buf, len);
11223     temp[len] = 0;
11224
11225     p = temp;
11226     while (*p) {
11227         if (*p == '\n' || *p == '\r')
11228           *p = ' ';
11229         ++p;
11230     }
11231
11232     strcat(temp, "\n");
11233     SendToICS(temp);
11234     SendToPlayer(temp, strlen(temp));
11235 }
11236
11237 void
11238 SetWhiteToPlayEvent()
11239 {
11240     if (gameMode == EditPosition) {
11241         blackPlaysFirst = FALSE;
11242         DisplayBothClocks();    /* works because currentMove is 0 */
11243     } else if (gameMode == IcsExamining) {
11244         SendToICS(ics_prefix);
11245         SendToICS("tomove white\n");
11246     }
11247 }
11248
11249 void
11250 SetBlackToPlayEvent()
11251 {
11252     if (gameMode == EditPosition) {
11253         blackPlaysFirst = TRUE;
11254         currentMove = 1;        /* kludge */
11255         DisplayBothClocks();
11256         currentMove = 0;
11257     } else if (gameMode == IcsExamining) {
11258         SendToICS(ics_prefix);
11259         SendToICS("tomove black\n");
11260     }
11261 }
11262
11263 void
11264 EditPositionMenuEvent(selection, x, y)
11265      ChessSquare selection;
11266      int x, y;
11267 {
11268     char buf[MSG_SIZ];
11269     ChessSquare piece = boards[0][y][x];
11270
11271     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11272
11273     switch (selection) {
11274       case ClearBoard:
11275         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11276             SendToICS(ics_prefix);
11277             SendToICS("bsetup clear\n");
11278         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11279             SendToICS(ics_prefix);
11280             SendToICS("clearboard\n");
11281         } else {
11282             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11283                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11284                 for (y = 0; y < BOARD_HEIGHT; y++) {
11285                     if (gameMode == IcsExamining) {
11286                         if (boards[currentMove][y][x] != EmptySquare) {
11287                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11288                                     AAA + x, ONE + y);
11289                             SendToICS(buf);
11290                         }
11291                     } else {
11292                         boards[0][y][x] = p;
11293                     }
11294                 }
11295             }
11296         }
11297         if (gameMode == EditPosition) {
11298             DrawPosition(FALSE, boards[0]);
11299         }
11300         break;
11301
11302       case WhitePlay:
11303         SetWhiteToPlayEvent();
11304         break;
11305
11306       case BlackPlay:
11307         SetBlackToPlayEvent();
11308         break;
11309
11310       case EmptySquare:
11311         if (gameMode == IcsExamining) {
11312             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11313             SendToICS(buf);
11314         } else {
11315             boards[0][y][x] = EmptySquare;
11316             DrawPosition(FALSE, boards[0]);
11317         }
11318         break;
11319
11320       case PromotePiece:
11321         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11322            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11323             selection = (ChessSquare) (PROMOTED piece);
11324         } else if(piece == EmptySquare) selection = WhiteSilver;
11325         else selection = (ChessSquare)((int)piece - 1);
11326         goto defaultlabel;
11327
11328       case DemotePiece:
11329         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11330            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11331             selection = (ChessSquare) (DEMOTED piece);
11332         } else if(piece == EmptySquare) selection = BlackSilver;
11333         else selection = (ChessSquare)((int)piece + 1);       
11334         goto defaultlabel;
11335
11336       case WhiteQueen:
11337       case BlackQueen:
11338         if(gameInfo.variant == VariantShatranj ||
11339            gameInfo.variant == VariantXiangqi  ||
11340            gameInfo.variant == VariantCourier    )
11341             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11342         goto defaultlabel;
11343
11344       case WhiteKing:
11345       case BlackKing:
11346         if(gameInfo.variant == VariantXiangqi)
11347             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11348         if(gameInfo.variant == VariantKnightmate)
11349             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11350       default:
11351         defaultlabel:
11352         if (gameMode == IcsExamining) {
11353             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11354                     PieceToChar(selection), AAA + x, ONE + y);
11355             SendToICS(buf);
11356         } else {
11357             boards[0][y][x] = selection;
11358             DrawPosition(FALSE, boards[0]);
11359         }
11360         break;
11361     }
11362 }
11363
11364
11365 void
11366 DropMenuEvent(selection, x, y)
11367      ChessSquare selection;
11368      int x, y;
11369 {
11370     ChessMove moveType;
11371
11372     switch (gameMode) {
11373       case IcsPlayingWhite:
11374       case MachinePlaysBlack:
11375         if (!WhiteOnMove(currentMove)) {
11376             DisplayMoveError(_("It is Black's turn"));
11377             return;
11378         }
11379         moveType = WhiteDrop;
11380         break;
11381       case IcsPlayingBlack:
11382       case MachinePlaysWhite:
11383         if (WhiteOnMove(currentMove)) {
11384             DisplayMoveError(_("It is White's turn"));
11385             return;
11386         }
11387         moveType = BlackDrop;
11388         break;
11389       case EditGame:
11390         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11391         break;
11392       default:
11393         return;
11394     }
11395
11396     if (moveType == BlackDrop && selection < BlackPawn) {
11397       selection = (ChessSquare) ((int) selection
11398                                  + (int) BlackPawn - (int) WhitePawn);
11399     }
11400     if (boards[currentMove][y][x] != EmptySquare) {
11401         DisplayMoveError(_("That square is occupied"));
11402         return;
11403     }
11404
11405     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11406 }
11407
11408 void
11409 AcceptEvent()
11410 {
11411     /* Accept a pending offer of any kind from opponent */
11412     
11413     if (appData.icsActive) {
11414         SendToICS(ics_prefix);
11415         SendToICS("accept\n");
11416     } else if (cmailMsgLoaded) {
11417         if (currentMove == cmailOldMove &&
11418             commentList[cmailOldMove] != NULL &&
11419             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11420                    "Black offers a draw" : "White offers a draw")) {
11421             TruncateGame();
11422             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11423             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11424         } else {
11425             DisplayError(_("There is no pending offer on this move"), 0);
11426             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11427         }
11428     } else {
11429         /* Not used for offers from chess program */
11430     }
11431 }
11432
11433 void
11434 DeclineEvent()
11435 {
11436     /* Decline a pending offer of any kind from opponent */
11437     
11438     if (appData.icsActive) {
11439         SendToICS(ics_prefix);
11440         SendToICS("decline\n");
11441     } else if (cmailMsgLoaded) {
11442         if (currentMove == cmailOldMove &&
11443             commentList[cmailOldMove] != NULL &&
11444             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11445                    "Black offers a draw" : "White offers a draw")) {
11446 #ifdef NOTDEF
11447             AppendComment(cmailOldMove, "Draw declined");
11448             DisplayComment(cmailOldMove - 1, "Draw declined");
11449 #endif /*NOTDEF*/
11450         } else {
11451             DisplayError(_("There is no pending offer on this move"), 0);
11452         }
11453     } else {
11454         /* Not used for offers from chess program */
11455     }
11456 }
11457
11458 void
11459 RematchEvent()
11460 {
11461     /* Issue ICS rematch command */
11462     if (appData.icsActive) {
11463         SendToICS(ics_prefix);
11464         SendToICS("rematch\n");
11465     }
11466 }
11467
11468 void
11469 CallFlagEvent()
11470 {
11471     /* Call your opponent's flag (claim a win on time) */
11472     if (appData.icsActive) {
11473         SendToICS(ics_prefix);
11474         SendToICS("flag\n");
11475     } else {
11476         switch (gameMode) {
11477           default:
11478             return;
11479           case MachinePlaysWhite:
11480             if (whiteFlag) {
11481                 if (blackFlag)
11482                   GameEnds(GameIsDrawn, "Both players ran out of time",
11483                            GE_PLAYER);
11484                 else
11485                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11486             } else {
11487                 DisplayError(_("Your opponent is not out of time"), 0);
11488             }
11489             break;
11490           case MachinePlaysBlack:
11491             if (blackFlag) {
11492                 if (whiteFlag)
11493                   GameEnds(GameIsDrawn, "Both players ran out of time",
11494                            GE_PLAYER);
11495                 else
11496                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11497             } else {
11498                 DisplayError(_("Your opponent is not out of time"), 0);
11499             }
11500             break;
11501         }
11502     }
11503 }
11504
11505 void
11506 DrawEvent()
11507 {
11508     /* Offer draw or accept pending draw offer from opponent */
11509     
11510     if (appData.icsActive) {
11511         /* Note: tournament rules require draw offers to be
11512            made after you make your move but before you punch
11513            your clock.  Currently ICS doesn't let you do that;
11514            instead, you immediately punch your clock after making
11515            a move, but you can offer a draw at any time. */
11516         
11517         SendToICS(ics_prefix);
11518         SendToICS("draw\n");
11519     } else if (cmailMsgLoaded) {
11520         if (currentMove == cmailOldMove &&
11521             commentList[cmailOldMove] != NULL &&
11522             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11523                    "Black offers a draw" : "White offers a draw")) {
11524             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11525             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11526         } else if (currentMove == cmailOldMove + 1) {
11527             char *offer = WhiteOnMove(cmailOldMove) ?
11528               "White offers a draw" : "Black offers a draw";
11529             AppendComment(currentMove, offer);
11530             DisplayComment(currentMove - 1, offer);
11531             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11532         } else {
11533             DisplayError(_("You must make your move before offering a draw"), 0);
11534             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11535         }
11536     } else if (first.offeredDraw) {
11537         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11538     } else {
11539         if (first.sendDrawOffers) {
11540             SendToProgram("draw\n", &first);
11541             userOfferedDraw = TRUE;
11542         }
11543     }
11544 }
11545
11546 void
11547 AdjournEvent()
11548 {
11549     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11550     
11551     if (appData.icsActive) {
11552         SendToICS(ics_prefix);
11553         SendToICS("adjourn\n");
11554     } else {
11555         /* Currently GNU Chess doesn't offer or accept Adjourns */
11556     }
11557 }
11558
11559
11560 void
11561 AbortEvent()
11562 {
11563     /* Offer Abort or accept pending Abort offer from opponent */
11564     
11565     if (appData.icsActive) {
11566         SendToICS(ics_prefix);
11567         SendToICS("abort\n");
11568     } else {
11569         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11570     }
11571 }
11572
11573 void
11574 ResignEvent()
11575 {
11576     /* Resign.  You can do this even if it's not your turn. */
11577     
11578     if (appData.icsActive) {
11579         SendToICS(ics_prefix);
11580         SendToICS("resign\n");
11581     } else {
11582         switch (gameMode) {
11583           case MachinePlaysWhite:
11584             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11585             break;
11586           case MachinePlaysBlack:
11587             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11588             break;
11589           case EditGame:
11590             if (cmailMsgLoaded) {
11591                 TruncateGame();
11592                 if (WhiteOnMove(cmailOldMove)) {
11593                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11594                 } else {
11595                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11596                 }
11597                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11598             }
11599             break;
11600           default:
11601             break;
11602         }
11603     }
11604 }
11605
11606
11607 void
11608 StopObservingEvent()
11609 {
11610     /* Stop observing current games */
11611     SendToICS(ics_prefix);
11612     SendToICS("unobserve\n");
11613 }
11614
11615 void
11616 StopExaminingEvent()
11617 {
11618     /* Stop observing current game */
11619     SendToICS(ics_prefix);
11620     SendToICS("unexamine\n");
11621 }
11622
11623 void
11624 ForwardInner(target)
11625      int target;
11626 {
11627     int limit;
11628
11629     if (appData.debugMode)
11630         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11631                 target, currentMove, forwardMostMove);
11632
11633     if (gameMode == EditPosition)
11634       return;
11635
11636     if (gameMode == PlayFromGameFile && !pausing)
11637       PauseEvent();
11638     
11639     if (gameMode == IcsExamining && pausing)
11640       limit = pauseExamForwardMostMove;
11641     else
11642       limit = forwardMostMove;
11643     
11644     if (target > limit) target = limit;
11645
11646     if (target > 0 && moveList[target - 1][0]) {
11647         int fromX, fromY, toX, toY;
11648         toX = moveList[target - 1][2] - AAA;
11649         toY = moveList[target - 1][3] - ONE;
11650         if (moveList[target - 1][1] == '@') {
11651             if (appData.highlightLastMove) {
11652                 SetHighlights(-1, -1, toX, toY);
11653             }
11654         } else {
11655             fromX = moveList[target - 1][0] - AAA;
11656             fromY = moveList[target - 1][1] - ONE;
11657             if (target == currentMove + 1) {
11658                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11659             }
11660             if (appData.highlightLastMove) {
11661                 SetHighlights(fromX, fromY, toX, toY);
11662             }
11663         }
11664     }
11665     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11666         gameMode == Training || gameMode == PlayFromGameFile || 
11667         gameMode == AnalyzeFile) {
11668         while (currentMove < target) {
11669             SendMoveToProgram(currentMove++, &first);
11670         }
11671     } else {
11672         currentMove = target;
11673     }
11674     
11675     if (gameMode == EditGame || gameMode == EndOfGame) {
11676         whiteTimeRemaining = timeRemaining[0][currentMove];
11677         blackTimeRemaining = timeRemaining[1][currentMove];
11678     }
11679     DisplayBothClocks();
11680     DisplayMove(currentMove - 1);
11681     DrawPosition(FALSE, boards[currentMove]);
11682     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11683     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11684         DisplayComment(currentMove - 1, commentList[currentMove]);
11685     }
11686 }
11687
11688
11689 void
11690 ForwardEvent()
11691 {
11692     if (gameMode == IcsExamining && !pausing) {
11693         SendToICS(ics_prefix);
11694         SendToICS("forward\n");
11695     } else {
11696         ForwardInner(currentMove + 1);
11697     }
11698 }
11699
11700 void
11701 ToEndEvent()
11702 {
11703     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11704         /* to optimze, we temporarily turn off analysis mode while we feed
11705          * the remaining moves to the engine. Otherwise we get analysis output
11706          * after each move.
11707          */ 
11708         if (first.analysisSupport) {
11709           SendToProgram("exit\nforce\n", &first);
11710           first.analyzing = FALSE;
11711         }
11712     }
11713         
11714     if (gameMode == IcsExamining && !pausing) {
11715         SendToICS(ics_prefix);
11716         SendToICS("forward 999999\n");
11717     } else {
11718         ForwardInner(forwardMostMove);
11719     }
11720
11721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11722         /* we have fed all the moves, so reactivate analysis mode */
11723         SendToProgram("analyze\n", &first);
11724         first.analyzing = TRUE;
11725         /*first.maybeThinking = TRUE;*/
11726         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11727     }
11728 }
11729
11730 void
11731 BackwardInner(target)
11732      int target;
11733 {
11734     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11735
11736     if (appData.debugMode)
11737         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11738                 target, currentMove, forwardMostMove);
11739
11740     if (gameMode == EditPosition) return;
11741     if (currentMove <= backwardMostMove) {
11742         ClearHighlights();
11743         DrawPosition(full_redraw, boards[currentMove]);
11744         return;
11745     }
11746     if (gameMode == PlayFromGameFile && !pausing)
11747       PauseEvent();
11748     
11749     if (moveList[target][0]) {
11750         int fromX, fromY, toX, toY;
11751         toX = moveList[target][2] - AAA;
11752         toY = moveList[target][3] - ONE;
11753         if (moveList[target][1] == '@') {
11754             if (appData.highlightLastMove) {
11755                 SetHighlights(-1, -1, toX, toY);
11756             }
11757         } else {
11758             fromX = moveList[target][0] - AAA;
11759             fromY = moveList[target][1] - ONE;
11760             if (target == currentMove - 1) {
11761                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11762             }
11763             if (appData.highlightLastMove) {
11764                 SetHighlights(fromX, fromY, toX, toY);
11765             }
11766         }
11767     }
11768     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11769         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11770         while (currentMove > target) {
11771             SendToProgram("undo\n", &first);
11772             currentMove--;
11773         }
11774     } else {
11775         currentMove = target;
11776     }
11777     
11778     if (gameMode == EditGame || gameMode == EndOfGame) {
11779         whiteTimeRemaining = timeRemaining[0][currentMove];
11780         blackTimeRemaining = timeRemaining[1][currentMove];
11781     }
11782     DisplayBothClocks();
11783     DisplayMove(currentMove - 1);
11784     DrawPosition(full_redraw, boards[currentMove]);
11785     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11786     // [HGM] PV info: routine tests if comment empty
11787     DisplayComment(currentMove - 1, commentList[currentMove]);
11788 }
11789
11790 void
11791 BackwardEvent()
11792 {
11793     if (gameMode == IcsExamining && !pausing) {
11794         SendToICS(ics_prefix);
11795         SendToICS("backward\n");
11796     } else {
11797         BackwardInner(currentMove - 1);
11798     }
11799 }
11800
11801 void
11802 ToStartEvent()
11803 {
11804     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11805         /* to optimze, we temporarily turn off analysis mode while we undo
11806          * all the moves. Otherwise we get analysis output after each undo.
11807          */ 
11808         if (first.analysisSupport) {
11809           SendToProgram("exit\nforce\n", &first);
11810           first.analyzing = FALSE;
11811         }
11812     }
11813
11814     if (gameMode == IcsExamining && !pausing) {
11815         SendToICS(ics_prefix);
11816         SendToICS("backward 999999\n");
11817     } else {
11818         BackwardInner(backwardMostMove);
11819     }
11820
11821     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11822         /* we have fed all the moves, so reactivate analysis mode */
11823         SendToProgram("analyze\n", &first);
11824         first.analyzing = TRUE;
11825         /*first.maybeThinking = TRUE;*/
11826         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11827     }
11828 }
11829
11830 void
11831 ToNrEvent(int to)
11832 {
11833   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11834   if (to >= forwardMostMove) to = forwardMostMove;
11835   if (to <= backwardMostMove) to = backwardMostMove;
11836   if (to < currentMove) {
11837     BackwardInner(to);
11838   } else {
11839     ForwardInner(to);
11840   }
11841 }
11842
11843 void
11844 RevertEvent()
11845 {
11846     if (gameMode != IcsExamining) {
11847         DisplayError(_("You are not examining a game"), 0);
11848         return;
11849     }
11850     if (pausing) {
11851         DisplayError(_("You can't revert while pausing"), 0);
11852         return;
11853     }
11854     SendToICS(ics_prefix);
11855     SendToICS("revert\n");
11856 }
11857
11858 void
11859 RetractMoveEvent()
11860 {
11861     switch (gameMode) {
11862       case MachinePlaysWhite:
11863       case MachinePlaysBlack:
11864         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11865             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11866             return;
11867         }
11868         if (forwardMostMove < 2) return;
11869         currentMove = forwardMostMove = forwardMostMove - 2;
11870         whiteTimeRemaining = timeRemaining[0][currentMove];
11871         blackTimeRemaining = timeRemaining[1][currentMove];
11872         DisplayBothClocks();
11873         DisplayMove(currentMove - 1);
11874         ClearHighlights();/*!! could figure this out*/
11875         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11876         SendToProgram("remove\n", &first);
11877         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11878         break;
11879
11880       case BeginningOfGame:
11881       default:
11882         break;
11883
11884       case IcsPlayingWhite:
11885       case IcsPlayingBlack:
11886         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11887             SendToICS(ics_prefix);
11888             SendToICS("takeback 2\n");
11889         } else {
11890             SendToICS(ics_prefix);
11891             SendToICS("takeback 1\n");
11892         }
11893         break;
11894     }
11895 }
11896
11897 void
11898 MoveNowEvent()
11899 {
11900     ChessProgramState *cps;
11901
11902     switch (gameMode) {
11903       case MachinePlaysWhite:
11904         if (!WhiteOnMove(forwardMostMove)) {
11905             DisplayError(_("It is your turn"), 0);
11906             return;
11907         }
11908         cps = &first;
11909         break;
11910       case MachinePlaysBlack:
11911         if (WhiteOnMove(forwardMostMove)) {
11912             DisplayError(_("It is your turn"), 0);
11913             return;
11914         }
11915         cps = &first;
11916         break;
11917       case TwoMachinesPlay:
11918         if (WhiteOnMove(forwardMostMove) ==
11919             (first.twoMachinesColor[0] == 'w')) {
11920             cps = &first;
11921         } else {
11922             cps = &second;
11923         }
11924         break;
11925       case BeginningOfGame:
11926       default:
11927         return;
11928     }
11929     SendToProgram("?\n", cps);
11930 }
11931
11932 void
11933 TruncateGameEvent()
11934 {
11935     EditGameEvent();
11936     if (gameMode != EditGame) return;
11937     TruncateGame();
11938 }
11939
11940 void
11941 TruncateGame()
11942 {
11943     if (forwardMostMove > currentMove) {
11944         if (gameInfo.resultDetails != NULL) {
11945             free(gameInfo.resultDetails);
11946             gameInfo.resultDetails = NULL;
11947             gameInfo.result = GameUnfinished;
11948         }
11949         forwardMostMove = currentMove;
11950         HistorySet(parseList, backwardMostMove, forwardMostMove,
11951                    currentMove-1);
11952     }
11953 }
11954
11955 void
11956 HintEvent()
11957 {
11958     if (appData.noChessProgram) return;
11959     switch (gameMode) {
11960       case MachinePlaysWhite:
11961         if (WhiteOnMove(forwardMostMove)) {
11962             DisplayError(_("Wait until your turn"), 0);
11963             return;
11964         }
11965         break;
11966       case BeginningOfGame:
11967       case MachinePlaysBlack:
11968         if (!WhiteOnMove(forwardMostMove)) {
11969             DisplayError(_("Wait until your turn"), 0);
11970             return;
11971         }
11972         break;
11973       default:
11974         DisplayError(_("No hint available"), 0);
11975         return;
11976     }
11977     SendToProgram("hint\n", &first);
11978     hintRequested = TRUE;
11979 }
11980
11981 void
11982 BookEvent()
11983 {
11984     if (appData.noChessProgram) return;
11985     switch (gameMode) {
11986       case MachinePlaysWhite:
11987         if (WhiteOnMove(forwardMostMove)) {
11988             DisplayError(_("Wait until your turn"), 0);
11989             return;
11990         }
11991         break;
11992       case BeginningOfGame:
11993       case MachinePlaysBlack:
11994         if (!WhiteOnMove(forwardMostMove)) {
11995             DisplayError(_("Wait until your turn"), 0);
11996             return;
11997         }
11998         break;
11999       case EditPosition:
12000         EditPositionDone();
12001         break;
12002       case TwoMachinesPlay:
12003         return;
12004       default:
12005         break;
12006     }
12007     SendToProgram("bk\n", &first);
12008     bookOutput[0] = NULLCHAR;
12009     bookRequested = TRUE;
12010 }
12011
12012 void
12013 AboutGameEvent()
12014 {
12015     char *tags = PGNTags(&gameInfo);
12016     TagsPopUp(tags, CmailMsg());
12017     free(tags);
12018 }
12019
12020 /* end button procedures */
12021
12022 void
12023 PrintPosition(fp, move)
12024      FILE *fp;
12025      int move;
12026 {
12027     int i, j;
12028     
12029     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12030         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12031             char c = PieceToChar(boards[move][i][j]);
12032             fputc(c == 'x' ? '.' : c, fp);
12033             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12034         }
12035     }
12036     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12037       fprintf(fp, "white to play\n");
12038     else
12039       fprintf(fp, "black to play\n");
12040 }
12041
12042 void
12043 PrintOpponents(fp)
12044      FILE *fp;
12045 {
12046     if (gameInfo.white != NULL) {
12047         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12048     } else {
12049         fprintf(fp, "\n");
12050     }
12051 }
12052
12053 /* Find last component of program's own name, using some heuristics */
12054 void
12055 TidyProgramName(prog, host, buf)
12056      char *prog, *host, buf[MSG_SIZ];
12057 {
12058     char *p, *q;
12059     int local = (strcmp(host, "localhost") == 0);
12060     while (!local && (p = strchr(prog, ';')) != NULL) {
12061         p++;
12062         while (*p == ' ') p++;
12063         prog = p;
12064     }
12065     if (*prog == '"' || *prog == '\'') {
12066         q = strchr(prog + 1, *prog);
12067     } else {
12068         q = strchr(prog, ' ');
12069     }
12070     if (q == NULL) q = prog + strlen(prog);
12071     p = q;
12072     while (p >= prog && *p != '/' && *p != '\\') p--;
12073     p++;
12074     if(p == prog && *p == '"') p++;
12075     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12076     memcpy(buf, p, q - p);
12077     buf[q - p] = NULLCHAR;
12078     if (!local) {
12079         strcat(buf, "@");
12080         strcat(buf, host);
12081     }
12082 }
12083
12084 char *
12085 TimeControlTagValue()
12086 {
12087     char buf[MSG_SIZ];
12088     if (!appData.clockMode) {
12089         strcpy(buf, "-");
12090     } else if (movesPerSession > 0) {
12091         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12092     } else if (timeIncrement == 0) {
12093         sprintf(buf, "%ld", timeControl/1000);
12094     } else {
12095         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12096     }
12097     return StrSave(buf);
12098 }
12099
12100 void
12101 SetGameInfo()
12102 {
12103     /* This routine is used only for certain modes */
12104     VariantClass v = gameInfo.variant;
12105     ClearGameInfo(&gameInfo);
12106     gameInfo.variant = v;
12107
12108     switch (gameMode) {
12109       case MachinePlaysWhite:
12110         gameInfo.event = StrSave( appData.pgnEventHeader );
12111         gameInfo.site = StrSave(HostName());
12112         gameInfo.date = PGNDate();
12113         gameInfo.round = StrSave("-");
12114         gameInfo.white = StrSave(first.tidy);
12115         gameInfo.black = StrSave(UserName());
12116         gameInfo.timeControl = TimeControlTagValue();
12117         break;
12118
12119       case MachinePlaysBlack:
12120         gameInfo.event = StrSave( appData.pgnEventHeader );
12121         gameInfo.site = StrSave(HostName());
12122         gameInfo.date = PGNDate();
12123         gameInfo.round = StrSave("-");
12124         gameInfo.white = StrSave(UserName());
12125         gameInfo.black = StrSave(first.tidy);
12126         gameInfo.timeControl = TimeControlTagValue();
12127         break;
12128
12129       case TwoMachinesPlay:
12130         gameInfo.event = StrSave( appData.pgnEventHeader );
12131         gameInfo.site = StrSave(HostName());
12132         gameInfo.date = PGNDate();
12133         if (matchGame > 0) {
12134             char buf[MSG_SIZ];
12135             sprintf(buf, "%d", matchGame);
12136             gameInfo.round = StrSave(buf);
12137         } else {
12138             gameInfo.round = StrSave("-");
12139         }
12140         if (first.twoMachinesColor[0] == 'w') {
12141             gameInfo.white = StrSave(first.tidy);
12142             gameInfo.black = StrSave(second.tidy);
12143         } else {
12144             gameInfo.white = StrSave(second.tidy);
12145             gameInfo.black = StrSave(first.tidy);
12146         }
12147         gameInfo.timeControl = TimeControlTagValue();
12148         break;
12149
12150       case EditGame:
12151         gameInfo.event = StrSave("Edited game");
12152         gameInfo.site = StrSave(HostName());
12153         gameInfo.date = PGNDate();
12154         gameInfo.round = StrSave("-");
12155         gameInfo.white = StrSave("-");
12156         gameInfo.black = StrSave("-");
12157         break;
12158
12159       case EditPosition:
12160         gameInfo.event = StrSave("Edited position");
12161         gameInfo.site = StrSave(HostName());
12162         gameInfo.date = PGNDate();
12163         gameInfo.round = StrSave("-");
12164         gameInfo.white = StrSave("-");
12165         gameInfo.black = StrSave("-");
12166         break;
12167
12168       case IcsPlayingWhite:
12169       case IcsPlayingBlack:
12170       case IcsObserving:
12171       case IcsExamining:
12172         break;
12173
12174       case PlayFromGameFile:
12175         gameInfo.event = StrSave("Game from non-PGN file");
12176         gameInfo.site = StrSave(HostName());
12177         gameInfo.date = PGNDate();
12178         gameInfo.round = StrSave("-");
12179         gameInfo.white = StrSave("?");
12180         gameInfo.black = StrSave("?");
12181         break;
12182
12183       default:
12184         break;
12185     }
12186 }
12187
12188 void
12189 ReplaceComment(index, text)
12190      int index;
12191      char *text;
12192 {
12193     int len;
12194
12195     while (*text == '\n') text++;
12196     len = strlen(text);
12197     while (len > 0 && text[len - 1] == '\n') len--;
12198
12199     if (commentList[index] != NULL)
12200       free(commentList[index]);
12201
12202     if (len == 0) {
12203         commentList[index] = NULL;
12204         return;
12205     }
12206     commentList[index] = (char *) malloc(len + 2);
12207     strncpy(commentList[index], text, len);
12208     commentList[index][len] = '\n';
12209     commentList[index][len + 1] = NULLCHAR;
12210 }
12211
12212 void
12213 CrushCRs(text)
12214      char *text;
12215 {
12216   char *p = text;
12217   char *q = text;
12218   char ch;
12219
12220   do {
12221     ch = *p++;
12222     if (ch == '\r') continue;
12223     *q++ = ch;
12224   } while (ch != '\0');
12225 }
12226
12227 void
12228 AppendComment(index, text)
12229      int index;
12230      char *text;
12231 {
12232     int oldlen, len;
12233     char *old;
12234
12235     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12236
12237     CrushCRs(text);
12238     while (*text == '\n') text++;
12239     len = strlen(text);
12240     while (len > 0 && text[len - 1] == '\n') len--;
12241
12242     if (len == 0) return;
12243
12244     if (commentList[index] != NULL) {
12245         old = commentList[index];
12246         oldlen = strlen(old);
12247         commentList[index] = (char *) malloc(oldlen + len + 2);
12248         strcpy(commentList[index], old);
12249         free(old);
12250         strncpy(&commentList[index][oldlen], text, len);
12251         commentList[index][oldlen + len] = '\n';
12252         commentList[index][oldlen + len + 1] = NULLCHAR;
12253     } else {
12254         commentList[index] = (char *) malloc(len + 2);
12255         strncpy(commentList[index], text, len);
12256         commentList[index][len] = '\n';
12257         commentList[index][len + 1] = NULLCHAR;
12258     }
12259 }
12260
12261 static char * FindStr( char * text, char * sub_text )
12262 {
12263     char * result = strstr( text, sub_text );
12264
12265     if( result != NULL ) {
12266         result += strlen( sub_text );
12267     }
12268
12269     return result;
12270 }
12271
12272 /* [AS] Try to extract PV info from PGN comment */
12273 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12274 char *GetInfoFromComment( int index, char * text )
12275 {
12276     char * sep = text;
12277
12278     if( text != NULL && index > 0 ) {
12279         int score = 0;
12280         int depth = 0;
12281         int time = -1, sec = 0, deci;
12282         char * s_eval = FindStr( text, "[%eval " );
12283         char * s_emt = FindStr( text, "[%emt " );
12284
12285         if( s_eval != NULL || s_emt != NULL ) {
12286             /* New style */
12287             char delim;
12288
12289             if( s_eval != NULL ) {
12290                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12291                     return text;
12292                 }
12293
12294                 if( delim != ']' ) {
12295                     return text;
12296                 }
12297             }
12298
12299             if( s_emt != NULL ) {
12300             }
12301         }
12302         else {
12303             /* We expect something like: [+|-]nnn.nn/dd */
12304             int score_lo = 0;
12305
12306             sep = strchr( text, '/' );
12307             if( sep == NULL || sep < (text+4) ) {
12308                 return text;
12309             }
12310
12311             time = -1; sec = -1; deci = -1;
12312             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12313                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12314                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12315                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12316                 return text;
12317             }
12318
12319             if( score_lo < 0 || score_lo >= 100 ) {
12320                 return text;
12321             }
12322
12323             if(sec >= 0) time = 600*time + 10*sec; else
12324             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12325
12326             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12327
12328             /* [HGM] PV time: now locate end of PV info */
12329             while( *++sep >= '0' && *sep <= '9'); // strip depth
12330             if(time >= 0)
12331             while( *++sep >= '0' && *sep <= '9'); // strip time
12332             if(sec >= 0)
12333             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12334             if(deci >= 0)
12335             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12336             while(*sep == ' ') sep++;
12337         }
12338
12339         if( depth <= 0 ) {
12340             return text;
12341         }
12342
12343         if( time < 0 ) {
12344             time = -1;
12345         }
12346
12347         pvInfoList[index-1].depth = depth;
12348         pvInfoList[index-1].score = score;
12349         pvInfoList[index-1].time  = 10*time; // centi-sec
12350     }
12351     return sep;
12352 }
12353
12354 void
12355 SendToProgram(message, cps)
12356      char *message;
12357      ChessProgramState *cps;
12358 {
12359     int count, outCount, error;
12360     char buf[MSG_SIZ];
12361
12362     if (cps->pr == NULL) return;
12363     Attention(cps);
12364     
12365     if (appData.debugMode) {
12366         TimeMark now;
12367         GetTimeMark(&now);
12368         fprintf(debugFP, "%ld >%-6s: %s", 
12369                 SubtractTimeMarks(&now, &programStartTime),
12370                 cps->which, message);
12371     }
12372     
12373     count = strlen(message);
12374     outCount = OutputToProcess(cps->pr, message, count, &error);
12375     if (outCount < count && !exiting 
12376                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12377         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12378         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12379             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12380                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12381                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12382             } else {
12383                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12384             }
12385             gameInfo.resultDetails = buf;
12386         }
12387         DisplayFatalError(buf, error, 1);
12388     }
12389 }
12390
12391 void
12392 ReceiveFromProgram(isr, closure, message, count, error)
12393      InputSourceRef isr;
12394      VOIDSTAR closure;
12395      char *message;
12396      int count;
12397      int error;
12398 {
12399     char *end_str;
12400     char buf[MSG_SIZ];
12401     ChessProgramState *cps = (ChessProgramState *)closure;
12402
12403     if (isr != cps->isr) return; /* Killed intentionally */
12404     if (count <= 0) {
12405         if (count == 0) {
12406             sprintf(buf,
12407                     _("Error: %s chess program (%s) exited unexpectedly"),
12408                     cps->which, cps->program);
12409         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12410                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12411                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12412                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12413                 } else {
12414                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12415                 }
12416                 gameInfo.resultDetails = buf;
12417             }
12418             RemoveInputSource(cps->isr);
12419             DisplayFatalError(buf, 0, 1);
12420         } else {
12421             sprintf(buf,
12422                     _("Error reading from %s chess program (%s)"),
12423                     cps->which, cps->program);
12424             RemoveInputSource(cps->isr);
12425
12426             /* [AS] Program is misbehaving badly... kill it */
12427             if( count == -2 ) {
12428                 DestroyChildProcess( cps->pr, 9 );
12429                 cps->pr = NoProc;
12430             }
12431
12432             DisplayFatalError(buf, error, 1);
12433         }
12434         return;
12435     }
12436     
12437     if ((end_str = strchr(message, '\r')) != NULL)
12438       *end_str = NULLCHAR;
12439     if ((end_str = strchr(message, '\n')) != NULL)
12440       *end_str = NULLCHAR;
12441     
12442     if (appData.debugMode) {
12443         TimeMark now; int print = 1;
12444         char *quote = ""; char c; int i;
12445
12446         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12447                 char start = message[0];
12448                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12449                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12450                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12451                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12452                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12453                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12454                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12455                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12456                         { quote = "# "; print = (appData.engineComments == 2); }
12457                 message[0] = start; // restore original message
12458         }
12459         if(print) {
12460                 GetTimeMark(&now);
12461                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12462                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12463                         quote,
12464                         message);
12465         }
12466     }
12467
12468     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12469     if (appData.icsEngineAnalyze) {
12470         if (strstr(message, "whisper") != NULL ||
12471              strstr(message, "kibitz") != NULL || 
12472             strstr(message, "tellics") != NULL) return;
12473     }
12474
12475     HandleMachineMove(message, cps);
12476 }
12477
12478
12479 void
12480 SendTimeControl(cps, mps, tc, inc, sd, st)
12481      ChessProgramState *cps;
12482      int mps, inc, sd, st;
12483      long tc;
12484 {
12485     char buf[MSG_SIZ];
12486     int seconds;
12487
12488     if( timeControl_2 > 0 ) {
12489         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12490             tc = timeControl_2;
12491         }
12492     }
12493     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12494     inc /= cps->timeOdds;
12495     st  /= cps->timeOdds;
12496
12497     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12498
12499     if (st > 0) {
12500       /* Set exact time per move, normally using st command */
12501       if (cps->stKludge) {
12502         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12503         seconds = st % 60;
12504         if (seconds == 0) {
12505           sprintf(buf, "level 1 %d\n", st/60);
12506         } else {
12507           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12508         }
12509       } else {
12510         sprintf(buf, "st %d\n", st);
12511       }
12512     } else {
12513       /* Set conventional or incremental time control, using level command */
12514       if (seconds == 0) {
12515         /* Note old gnuchess bug -- minutes:seconds used to not work.
12516            Fixed in later versions, but still avoid :seconds
12517            when seconds is 0. */
12518         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12519       } else {
12520         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12521                 seconds, inc/1000);
12522       }
12523     }
12524     SendToProgram(buf, cps);
12525
12526     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12527     /* Orthogonally, limit search to given depth */
12528     if (sd > 0) {
12529       if (cps->sdKludge) {
12530         sprintf(buf, "depth\n%d\n", sd);
12531       } else {
12532         sprintf(buf, "sd %d\n", sd);
12533       }
12534       SendToProgram(buf, cps);
12535     }
12536
12537     if(cps->nps > 0) { /* [HGM] nps */
12538         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12539         else {
12540                 sprintf(buf, "nps %d\n", cps->nps);
12541               SendToProgram(buf, cps);
12542         }
12543     }
12544 }
12545
12546 ChessProgramState *WhitePlayer()
12547 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12548 {
12549     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12550        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12551         return &second;
12552     return &first;
12553 }
12554
12555 void
12556 SendTimeRemaining(cps, machineWhite)
12557      ChessProgramState *cps;
12558      int /*boolean*/ machineWhite;
12559 {
12560     char message[MSG_SIZ];
12561     long time, otime;
12562
12563     /* Note: this routine must be called when the clocks are stopped
12564        or when they have *just* been set or switched; otherwise
12565        it will be off by the time since the current tick started.
12566     */
12567     if (machineWhite) {
12568         time = whiteTimeRemaining / 10;
12569         otime = blackTimeRemaining / 10;
12570     } else {
12571         time = blackTimeRemaining / 10;
12572         otime = whiteTimeRemaining / 10;
12573     }
12574     /* [HGM] translate opponent's time by time-odds factor */
12575     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12576     if (appData.debugMode) {
12577         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12578     }
12579
12580     if (time <= 0) time = 1;
12581     if (otime <= 0) otime = 1;
12582     
12583     sprintf(message, "time %ld\n", time);
12584     SendToProgram(message, cps);
12585
12586     sprintf(message, "otim %ld\n", otime);
12587     SendToProgram(message, cps);
12588 }
12589
12590 int
12591 BoolFeature(p, name, loc, cps)
12592      char **p;
12593      char *name;
12594      int *loc;
12595      ChessProgramState *cps;
12596 {
12597   char buf[MSG_SIZ];
12598   int len = strlen(name);
12599   int val;
12600   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12601     (*p) += len + 1;
12602     sscanf(*p, "%d", &val);
12603     *loc = (val != 0);
12604     while (**p && **p != ' ') (*p)++;
12605     sprintf(buf, "accepted %s\n", name);
12606     SendToProgram(buf, cps);
12607     return TRUE;
12608   }
12609   return FALSE;
12610 }
12611
12612 int
12613 IntFeature(p, name, loc, cps)
12614      char **p;
12615      char *name;
12616      int *loc;
12617      ChessProgramState *cps;
12618 {
12619   char buf[MSG_SIZ];
12620   int len = strlen(name);
12621   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12622     (*p) += len + 1;
12623     sscanf(*p, "%d", loc);
12624     while (**p && **p != ' ') (*p)++;
12625     sprintf(buf, "accepted %s\n", name);
12626     SendToProgram(buf, cps);
12627     return TRUE;
12628   }
12629   return FALSE;
12630 }
12631
12632 int
12633 StringFeature(p, name, loc, cps)
12634      char **p;
12635      char *name;
12636      char loc[];
12637      ChessProgramState *cps;
12638 {
12639   char buf[MSG_SIZ];
12640   int len = strlen(name);
12641   if (strncmp((*p), name, len) == 0
12642       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12643     (*p) += len + 2;
12644     sscanf(*p, "%[^\"]", loc);
12645     while (**p && **p != '\"') (*p)++;
12646     if (**p == '\"') (*p)++;
12647     sprintf(buf, "accepted %s\n", name);
12648     SendToProgram(buf, cps);
12649     return TRUE;
12650   }
12651   return FALSE;
12652 }
12653
12654 int 
12655 ParseOption(Option *opt, ChessProgramState *cps)
12656 // [HGM] options: process the string that defines an engine option, and determine
12657 // name, type, default value, and allowed value range
12658 {
12659         char *p, *q, buf[MSG_SIZ];
12660         int n, min = (-1)<<31, max = 1<<31, def;
12661
12662         if(p = strstr(opt->name, " -spin ")) {
12663             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12664             if(max < min) max = min; // enforce consistency
12665             if(def < min) def = min;
12666             if(def > max) def = max;
12667             opt->value = def;
12668             opt->min = min;
12669             opt->max = max;
12670             opt->type = Spin;
12671         } else if((p = strstr(opt->name, " -slider "))) {
12672             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12673             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12674             if(max < min) max = min; // enforce consistency
12675             if(def < min) def = min;
12676             if(def > max) def = max;
12677             opt->value = def;
12678             opt->min = min;
12679             opt->max = max;
12680             opt->type = Spin; // Slider;
12681         } else if((p = strstr(opt->name, " -string "))) {
12682             opt->textValue = p+9;
12683             opt->type = TextBox;
12684         } else if((p = strstr(opt->name, " -file "))) {
12685             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12686             opt->textValue = p+7;
12687             opt->type = TextBox; // FileName;
12688         } else if((p = strstr(opt->name, " -path "))) {
12689             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12690             opt->textValue = p+7;
12691             opt->type = TextBox; // PathName;
12692         } else if(p = strstr(opt->name, " -check ")) {
12693             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12694             opt->value = (def != 0);
12695             opt->type = CheckBox;
12696         } else if(p = strstr(opt->name, " -combo ")) {
12697             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12698             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12699             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12700             opt->value = n = 0;
12701             while(q = StrStr(q, " /// ")) {
12702                 n++; *q = 0;    // count choices, and null-terminate each of them
12703                 q += 5;
12704                 if(*q == '*') { // remember default, which is marked with * prefix
12705                     q++;
12706                     opt->value = n;
12707                 }
12708                 cps->comboList[cps->comboCnt++] = q;
12709             }
12710             cps->comboList[cps->comboCnt++] = NULL;
12711             opt->max = n + 1;
12712             opt->type = ComboBox;
12713         } else if(p = strstr(opt->name, " -button")) {
12714             opt->type = Button;
12715         } else if(p = strstr(opt->name, " -save")) {
12716             opt->type = SaveButton;
12717         } else return FALSE;
12718         *p = 0; // terminate option name
12719         // now look if the command-line options define a setting for this engine option.
12720         if(cps->optionSettings && cps->optionSettings[0])
12721             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12722         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12723                 sprintf(buf, "option %s", p);
12724                 if(p = strstr(buf, ",")) *p = 0;
12725                 strcat(buf, "\n");
12726                 SendToProgram(buf, cps);
12727         }
12728         return TRUE;
12729 }
12730
12731 void
12732 FeatureDone(cps, val)
12733      ChessProgramState* cps;
12734      int val;
12735 {
12736   DelayedEventCallback cb = GetDelayedEvent();
12737   if ((cb == InitBackEnd3 && cps == &first) ||
12738       (cb == TwoMachinesEventIfReady && cps == &second)) {
12739     CancelDelayedEvent();
12740     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12741   }
12742   cps->initDone = val;
12743 }
12744
12745 /* Parse feature command from engine */
12746 void
12747 ParseFeatures(args, cps)
12748      char* args;
12749      ChessProgramState *cps;  
12750 {
12751   char *p = args;
12752   char *q;
12753   int val;
12754   char buf[MSG_SIZ];
12755
12756   for (;;) {
12757     while (*p == ' ') p++;
12758     if (*p == NULLCHAR) return;
12759
12760     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12761     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12762     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12763     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12764     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12765     if (BoolFeature(&p, "reuse", &val, cps)) {
12766       /* Engine can disable reuse, but can't enable it if user said no */
12767       if (!val) cps->reuse = FALSE;
12768       continue;
12769     }
12770     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12771     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12772       if (gameMode == TwoMachinesPlay) {
12773         DisplayTwoMachinesTitle();
12774       } else {
12775         DisplayTitle("");
12776       }
12777       continue;
12778     }
12779     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12780     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12781     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12782     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12783     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12784     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12785     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12786     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12787     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12788     if (IntFeature(&p, "done", &val, cps)) {
12789       FeatureDone(cps, val);
12790       continue;
12791     }
12792     /* Added by Tord: */
12793     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12794     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12795     /* End of additions by Tord */
12796
12797     /* [HGM] added features: */
12798     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12799     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12800     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12801     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12802     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12803     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12804     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12805         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12806             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12807             SendToProgram(buf, cps);
12808             continue;
12809         }
12810         if(cps->nrOptions >= MAX_OPTIONS) {
12811             cps->nrOptions--;
12812             sprintf(buf, "%s engine has too many options\n", cps->which);
12813             DisplayError(buf, 0);
12814         }
12815         continue;
12816     }
12817     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12818     /* End of additions by HGM */
12819
12820     /* unknown feature: complain and skip */
12821     q = p;
12822     while (*q && *q != '=') q++;
12823     sprintf(buf, "rejected %.*s\n", q-p, p);
12824     SendToProgram(buf, cps);
12825     p = q;
12826     if (*p == '=') {
12827       p++;
12828       if (*p == '\"') {
12829         p++;
12830         while (*p && *p != '\"') p++;
12831         if (*p == '\"') p++;
12832       } else {
12833         while (*p && *p != ' ') p++;
12834       }
12835     }
12836   }
12837
12838 }
12839
12840 void
12841 PeriodicUpdatesEvent(newState)
12842      int newState;
12843 {
12844     if (newState == appData.periodicUpdates)
12845       return;
12846
12847     appData.periodicUpdates=newState;
12848
12849     /* Display type changes, so update it now */
12850 //    DisplayAnalysis();
12851
12852     /* Get the ball rolling again... */
12853     if (newState) {
12854         AnalysisPeriodicEvent(1);
12855         StartAnalysisClock();
12856     }
12857 }
12858
12859 void
12860 PonderNextMoveEvent(newState)
12861      int newState;
12862 {
12863     if (newState == appData.ponderNextMove) return;
12864     if (gameMode == EditPosition) EditPositionDone();
12865     if (newState) {
12866         SendToProgram("hard\n", &first);
12867         if (gameMode == TwoMachinesPlay) {
12868             SendToProgram("hard\n", &second);
12869         }
12870     } else {
12871         SendToProgram("easy\n", &first);
12872         thinkOutput[0] = NULLCHAR;
12873         if (gameMode == TwoMachinesPlay) {
12874             SendToProgram("easy\n", &second);
12875         }
12876     }
12877     appData.ponderNextMove = newState;
12878 }
12879
12880 void
12881 NewSettingEvent(option, command, value)
12882      char *command;
12883      int option, value;
12884 {
12885     char buf[MSG_SIZ];
12886
12887     if (gameMode == EditPosition) EditPositionDone();
12888     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12889     SendToProgram(buf, &first);
12890     if (gameMode == TwoMachinesPlay) {
12891         SendToProgram(buf, &second);
12892     }
12893 }
12894
12895 void
12896 ShowThinkingEvent()
12897 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12898 {
12899     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12900     int newState = appData.showThinking
12901         // [HGM] thinking: other features now need thinking output as well
12902         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12903     
12904     if (oldState == newState) return;
12905     oldState = newState;
12906     if (gameMode == EditPosition) EditPositionDone();
12907     if (oldState) {
12908         SendToProgram("post\n", &first);
12909         if (gameMode == TwoMachinesPlay) {
12910             SendToProgram("post\n", &second);
12911         }
12912     } else {
12913         SendToProgram("nopost\n", &first);
12914         thinkOutput[0] = NULLCHAR;
12915         if (gameMode == TwoMachinesPlay) {
12916             SendToProgram("nopost\n", &second);
12917         }
12918     }
12919 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12920 }
12921
12922 void
12923 AskQuestionEvent(title, question, replyPrefix, which)
12924      char *title; char *question; char *replyPrefix; char *which;
12925 {
12926   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12927   if (pr == NoProc) return;
12928   AskQuestion(title, question, replyPrefix, pr);
12929 }
12930
12931 void
12932 DisplayMove(moveNumber)
12933      int moveNumber;
12934 {
12935     char message[MSG_SIZ];
12936     char res[MSG_SIZ];
12937     char cpThinkOutput[MSG_SIZ];
12938
12939     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12940     
12941     if (moveNumber == forwardMostMove - 1 || 
12942         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12943
12944         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12945
12946         if (strchr(cpThinkOutput, '\n')) {
12947             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12948         }
12949     } else {
12950         *cpThinkOutput = NULLCHAR;
12951     }
12952
12953     /* [AS] Hide thinking from human user */
12954     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12955         *cpThinkOutput = NULLCHAR;
12956         if( thinkOutput[0] != NULLCHAR ) {
12957             int i;
12958
12959             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12960                 cpThinkOutput[i] = '.';
12961             }
12962             cpThinkOutput[i] = NULLCHAR;
12963             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12964         }
12965     }
12966
12967     if (moveNumber == forwardMostMove - 1 &&
12968         gameInfo.resultDetails != NULL) {
12969         if (gameInfo.resultDetails[0] == NULLCHAR) {
12970             sprintf(res, " %s", PGNResult(gameInfo.result));
12971         } else {
12972             sprintf(res, " {%s} %s",
12973                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12974         }
12975     } else {
12976         res[0] = NULLCHAR;
12977     }
12978
12979     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12980         DisplayMessage(res, cpThinkOutput);
12981     } else {
12982         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12983                 WhiteOnMove(moveNumber) ? " " : ".. ",
12984                 parseList[moveNumber], res);
12985         DisplayMessage(message, cpThinkOutput);
12986     }
12987 }
12988
12989 void
12990 DisplayComment(moveNumber, text)
12991      int moveNumber;
12992      char *text;
12993 {
12994     char title[MSG_SIZ];
12995     char buf[8000]; // comment can be long!
12996     int score, depth;
12997
12998     if( appData.autoDisplayComment ) {
12999         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13000             strcpy(title, "Comment");
13001         } else {
13002             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13003                     WhiteOnMove(moveNumber) ? " " : ".. ",
13004                     parseList[moveNumber]);
13005         }
13006         // [HGM] PV info: display PV info together with (or as) comment
13007         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13008             if(text == NULL) text = "";                                           
13009             score = pvInfoList[moveNumber].score;
13010             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13011                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13012             text = buf;
13013         }
13014     } else title[0] = 0;
13015
13016     if (text != NULL)
13017         CommentPopUp(title, text);
13018 }
13019
13020 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13021  * might be busy thinking or pondering.  It can be omitted if your
13022  * gnuchess is configured to stop thinking immediately on any user
13023  * input.  However, that gnuchess feature depends on the FIONREAD
13024  * ioctl, which does not work properly on some flavors of Unix.
13025  */
13026 void
13027 Attention(cps)
13028      ChessProgramState *cps;
13029 {
13030 #if ATTENTION
13031     if (!cps->useSigint) return;
13032     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13033     switch (gameMode) {
13034       case MachinePlaysWhite:
13035       case MachinePlaysBlack:
13036       case TwoMachinesPlay:
13037       case IcsPlayingWhite:
13038       case IcsPlayingBlack:
13039       case AnalyzeMode:
13040       case AnalyzeFile:
13041         /* Skip if we know it isn't thinking */
13042         if (!cps->maybeThinking) return;
13043         if (appData.debugMode)
13044           fprintf(debugFP, "Interrupting %s\n", cps->which);
13045         InterruptChildProcess(cps->pr);
13046         cps->maybeThinking = FALSE;
13047         break;
13048       default:
13049         break;
13050     }
13051 #endif /*ATTENTION*/
13052 }
13053
13054 int
13055 CheckFlags()
13056 {
13057     if (whiteTimeRemaining <= 0) {
13058         if (!whiteFlag) {
13059             whiteFlag = TRUE;
13060             if (appData.icsActive) {
13061                 if (appData.autoCallFlag &&
13062                     gameMode == IcsPlayingBlack && !blackFlag) {
13063                   SendToICS(ics_prefix);
13064                   SendToICS("flag\n");
13065                 }
13066             } else {
13067                 if (blackFlag) {
13068                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13069                 } else {
13070                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13071                     if (appData.autoCallFlag) {
13072                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13073                         return TRUE;
13074                     }
13075                 }
13076             }
13077         }
13078     }
13079     if (blackTimeRemaining <= 0) {
13080         if (!blackFlag) {
13081             blackFlag = TRUE;
13082             if (appData.icsActive) {
13083                 if (appData.autoCallFlag &&
13084                     gameMode == IcsPlayingWhite && !whiteFlag) {
13085                   SendToICS(ics_prefix);
13086                   SendToICS("flag\n");
13087                 }
13088             } else {
13089                 if (whiteFlag) {
13090                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13091                 } else {
13092                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13093                     if (appData.autoCallFlag) {
13094                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13095                         return TRUE;
13096                     }
13097                 }
13098             }
13099         }
13100     }
13101     return FALSE;
13102 }
13103
13104 void
13105 CheckTimeControl()
13106 {
13107     if (!appData.clockMode || appData.icsActive ||
13108         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13109
13110     /*
13111      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13112      */
13113     if ( !WhiteOnMove(forwardMostMove) )
13114         /* White made time control */
13115         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13116         /* [HGM] time odds: correct new time quota for time odds! */
13117                                             / WhitePlayer()->timeOdds;
13118       else
13119         /* Black made time control */
13120         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13121                                             / WhitePlayer()->other->timeOdds;
13122 }
13123
13124 void
13125 DisplayBothClocks()
13126 {
13127     int wom = gameMode == EditPosition ?
13128       !blackPlaysFirst : WhiteOnMove(currentMove);
13129     DisplayWhiteClock(whiteTimeRemaining, wom);
13130     DisplayBlackClock(blackTimeRemaining, !wom);
13131 }
13132
13133
13134 /* Timekeeping seems to be a portability nightmare.  I think everyone
13135    has ftime(), but I'm really not sure, so I'm including some ifdefs
13136    to use other calls if you don't.  Clocks will be less accurate if
13137    you have neither ftime nor gettimeofday.
13138 */
13139
13140 /* VS 2008 requires the #include outside of the function */
13141 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13142 #include <sys/timeb.h>
13143 #endif
13144
13145 /* Get the current time as a TimeMark */
13146 void
13147 GetTimeMark(tm)
13148      TimeMark *tm;
13149 {
13150 #if HAVE_GETTIMEOFDAY
13151
13152     struct timeval timeVal;
13153     struct timezone timeZone;
13154
13155     gettimeofday(&timeVal, &timeZone);
13156     tm->sec = (long) timeVal.tv_sec; 
13157     tm->ms = (int) (timeVal.tv_usec / 1000L);
13158
13159 #else /*!HAVE_GETTIMEOFDAY*/
13160 #if HAVE_FTIME
13161
13162 // include <sys/timeb.h> / moved to just above start of function
13163     struct timeb timeB;
13164
13165     ftime(&timeB);
13166     tm->sec = (long) timeB.time;
13167     tm->ms = (int) timeB.millitm;
13168
13169 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13170     tm->sec = (long) time(NULL);
13171     tm->ms = 0;
13172 #endif
13173 #endif
13174 }
13175
13176 /* Return the difference in milliseconds between two
13177    time marks.  We assume the difference will fit in a long!
13178 */
13179 long
13180 SubtractTimeMarks(tm2, tm1)
13181      TimeMark *tm2, *tm1;
13182 {
13183     return 1000L*(tm2->sec - tm1->sec) +
13184            (long) (tm2->ms - tm1->ms);
13185 }
13186
13187
13188 /*
13189  * Code to manage the game clocks.
13190  *
13191  * In tournament play, black starts the clock and then white makes a move.
13192  * We give the human user a slight advantage if he is playing white---the
13193  * clocks don't run until he makes his first move, so it takes zero time.
13194  * Also, we don't account for network lag, so we could get out of sync
13195  * with GNU Chess's clock -- but then, referees are always right.  
13196  */
13197
13198 static TimeMark tickStartTM;
13199 static long intendedTickLength;
13200
13201 long
13202 NextTickLength(timeRemaining)
13203      long timeRemaining;
13204 {
13205     long nominalTickLength, nextTickLength;
13206
13207     if (timeRemaining > 0L && timeRemaining <= 10000L)
13208       nominalTickLength = 100L;
13209     else
13210       nominalTickLength = 1000L;
13211     nextTickLength = timeRemaining % nominalTickLength;
13212     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13213
13214     return nextTickLength;
13215 }
13216
13217 /* Adjust clock one minute up or down */
13218 void
13219 AdjustClock(Boolean which, int dir)
13220 {
13221     if(which) blackTimeRemaining += 60000*dir;
13222     else      whiteTimeRemaining += 60000*dir;
13223     DisplayBothClocks();
13224 }
13225
13226 /* Stop clocks and reset to a fresh time control */
13227 void
13228 ResetClocks() 
13229 {
13230     (void) StopClockTimer();
13231     if (appData.icsActive) {
13232         whiteTimeRemaining = blackTimeRemaining = 0;
13233     } else { /* [HGM] correct new time quote for time odds */
13234         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13235         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13236     }
13237     if (whiteFlag || blackFlag) {
13238         DisplayTitle("");
13239         whiteFlag = blackFlag = FALSE;
13240     }
13241     DisplayBothClocks();
13242 }
13243
13244 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13245
13246 /* Decrement running clock by amount of time that has passed */
13247 void
13248 DecrementClocks()
13249 {
13250     long timeRemaining;
13251     long lastTickLength, fudge;
13252     TimeMark now;
13253
13254     if (!appData.clockMode) return;
13255     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13256         
13257     GetTimeMark(&now);
13258
13259     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13260
13261     /* Fudge if we woke up a little too soon */
13262     fudge = intendedTickLength - lastTickLength;
13263     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13264
13265     if (WhiteOnMove(forwardMostMove)) {
13266         if(whiteNPS >= 0) lastTickLength = 0;
13267         timeRemaining = whiteTimeRemaining -= lastTickLength;
13268         DisplayWhiteClock(whiteTimeRemaining - fudge,
13269                           WhiteOnMove(currentMove));
13270     } else {
13271         if(blackNPS >= 0) lastTickLength = 0;
13272         timeRemaining = blackTimeRemaining -= lastTickLength;
13273         DisplayBlackClock(blackTimeRemaining - fudge,
13274                           !WhiteOnMove(currentMove));
13275     }
13276
13277     if (CheckFlags()) return;
13278         
13279     tickStartTM = now;
13280     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13281     StartClockTimer(intendedTickLength);
13282
13283     /* if the time remaining has fallen below the alarm threshold, sound the
13284      * alarm. if the alarm has sounded and (due to a takeback or time control
13285      * with increment) the time remaining has increased to a level above the
13286      * threshold, reset the alarm so it can sound again. 
13287      */
13288     
13289     if (appData.icsActive && appData.icsAlarm) {
13290
13291         /* make sure we are dealing with the user's clock */
13292         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13293                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13294            )) return;
13295
13296         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13297             alarmSounded = FALSE;
13298         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13299             PlayAlarmSound();
13300             alarmSounded = TRUE;
13301         }
13302     }
13303 }
13304
13305
13306 /* A player has just moved, so stop the previously running
13307    clock and (if in clock mode) start the other one.
13308    We redisplay both clocks in case we're in ICS mode, because
13309    ICS gives us an update to both clocks after every move.
13310    Note that this routine is called *after* forwardMostMove
13311    is updated, so the last fractional tick must be subtracted
13312    from the color that is *not* on move now.
13313 */
13314 void
13315 SwitchClocks()
13316 {
13317     long lastTickLength;
13318     TimeMark now;
13319     int flagged = FALSE;
13320
13321     GetTimeMark(&now);
13322
13323     if (StopClockTimer() && appData.clockMode) {
13324         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13325         if (WhiteOnMove(forwardMostMove)) {
13326             if(blackNPS >= 0) lastTickLength = 0;
13327             blackTimeRemaining -= lastTickLength;
13328            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13329 //         if(pvInfoList[forwardMostMove-1].time == -1)
13330                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13331                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13332         } else {
13333            if(whiteNPS >= 0) lastTickLength = 0;
13334            whiteTimeRemaining -= lastTickLength;
13335            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13336 //         if(pvInfoList[forwardMostMove-1].time == -1)
13337                  pvInfoList[forwardMostMove-1].time = 
13338                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13339         }
13340         flagged = CheckFlags();
13341     }
13342     CheckTimeControl();
13343
13344     if (flagged || !appData.clockMode) return;
13345
13346     switch (gameMode) {
13347       case MachinePlaysBlack:
13348       case MachinePlaysWhite:
13349       case BeginningOfGame:
13350         if (pausing) return;
13351         break;
13352
13353       case EditGame:
13354       case PlayFromGameFile:
13355       case IcsExamining:
13356         return;
13357
13358       default:
13359         break;
13360     }
13361
13362     tickStartTM = now;
13363     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13364       whiteTimeRemaining : blackTimeRemaining);
13365     StartClockTimer(intendedTickLength);
13366 }
13367         
13368
13369 /* Stop both clocks */
13370 void
13371 StopClocks()
13372 {       
13373     long lastTickLength;
13374     TimeMark now;
13375
13376     if (!StopClockTimer()) return;
13377     if (!appData.clockMode) return;
13378
13379     GetTimeMark(&now);
13380
13381     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13382     if (WhiteOnMove(forwardMostMove)) {
13383         if(whiteNPS >= 0) lastTickLength = 0;
13384         whiteTimeRemaining -= lastTickLength;
13385         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13386     } else {
13387         if(blackNPS >= 0) lastTickLength = 0;
13388         blackTimeRemaining -= lastTickLength;
13389         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13390     }
13391     CheckFlags();
13392 }
13393         
13394 /* Start clock of player on move.  Time may have been reset, so
13395    if clock is already running, stop and restart it. */
13396 void
13397 StartClocks()
13398 {
13399     (void) StopClockTimer(); /* in case it was running already */
13400     DisplayBothClocks();
13401     if (CheckFlags()) return;
13402
13403     if (!appData.clockMode) return;
13404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13405
13406     GetTimeMark(&tickStartTM);
13407     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13408       whiteTimeRemaining : blackTimeRemaining);
13409
13410    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13411     whiteNPS = blackNPS = -1; 
13412     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13413        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13414         whiteNPS = first.nps;
13415     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13416        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13417         blackNPS = first.nps;
13418     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13419         whiteNPS = second.nps;
13420     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13421         blackNPS = second.nps;
13422     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13423
13424     StartClockTimer(intendedTickLength);
13425 }
13426
13427 char *
13428 TimeString(ms)
13429      long ms;
13430 {
13431     long second, minute, hour, day;
13432     char *sign = "";
13433     static char buf[32];
13434     
13435     if (ms > 0 && ms <= 9900) {
13436       /* convert milliseconds to tenths, rounding up */
13437       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13438
13439       sprintf(buf, " %03.1f ", tenths/10.0);
13440       return buf;
13441     }
13442
13443     /* convert milliseconds to seconds, rounding up */
13444     /* use floating point to avoid strangeness of integer division
13445        with negative dividends on many machines */
13446     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13447
13448     if (second < 0) {
13449         sign = "-";
13450         second = -second;
13451     }
13452     
13453     day = second / (60 * 60 * 24);
13454     second = second % (60 * 60 * 24);
13455     hour = second / (60 * 60);
13456     second = second % (60 * 60);
13457     minute = second / 60;
13458     second = second % 60;
13459     
13460     if (day > 0)
13461       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13462               sign, day, hour, minute, second);
13463     else if (hour > 0)
13464       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13465     else
13466       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13467     
13468     return buf;
13469 }
13470
13471
13472 /*
13473  * This is necessary because some C libraries aren't ANSI C compliant yet.
13474  */
13475 char *
13476 StrStr(string, match)
13477      char *string, *match;
13478 {
13479     int i, length;
13480     
13481     length = strlen(match);
13482     
13483     for (i = strlen(string) - length; i >= 0; i--, string++)
13484       if (!strncmp(match, string, length))
13485         return string;
13486     
13487     return NULL;
13488 }
13489
13490 char *
13491 StrCaseStr(string, match)
13492      char *string, *match;
13493 {
13494     int i, j, length;
13495     
13496     length = strlen(match);
13497     
13498     for (i = strlen(string) - length; i >= 0; i--, string++) {
13499         for (j = 0; j < length; j++) {
13500             if (ToLower(match[j]) != ToLower(string[j]))
13501               break;
13502         }
13503         if (j == length) return string;
13504     }
13505
13506     return NULL;
13507 }
13508
13509 #ifndef _amigados
13510 int
13511 StrCaseCmp(s1, s2)
13512      char *s1, *s2;
13513 {
13514     char c1, c2;
13515     
13516     for (;;) {
13517         c1 = ToLower(*s1++);
13518         c2 = ToLower(*s2++);
13519         if (c1 > c2) return 1;
13520         if (c1 < c2) return -1;
13521         if (c1 == NULLCHAR) return 0;
13522     }
13523 }
13524
13525
13526 int
13527 ToLower(c)
13528      int c;
13529 {
13530     return isupper(c) ? tolower(c) : c;
13531 }
13532
13533
13534 int
13535 ToUpper(c)
13536      int c;
13537 {
13538     return islower(c) ? toupper(c) : c;
13539 }
13540 #endif /* !_amigados    */
13541
13542 char *
13543 StrSave(s)
13544      char *s;
13545 {
13546     char *ret;
13547
13548     if ((ret = (char *) malloc(strlen(s) + 1))) {
13549         strcpy(ret, s);
13550     }
13551     return ret;
13552 }
13553
13554 char *
13555 StrSavePtr(s, savePtr)
13556      char *s, **savePtr;
13557 {
13558     if (*savePtr) {
13559         free(*savePtr);
13560     }
13561     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13562         strcpy(*savePtr, s);
13563     }
13564     return(*savePtr);
13565 }
13566
13567 char *
13568 PGNDate()
13569 {
13570     time_t clock;
13571     struct tm *tm;
13572     char buf[MSG_SIZ];
13573
13574     clock = time((time_t *)NULL);
13575     tm = localtime(&clock);
13576     sprintf(buf, "%04d.%02d.%02d",
13577             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13578     return StrSave(buf);
13579 }
13580
13581
13582 char *
13583 PositionToFEN(move, overrideCastling)
13584      int move;
13585      char *overrideCastling;
13586 {
13587     int i, j, fromX, fromY, toX, toY;
13588     int whiteToPlay;
13589     char buf[128];
13590     char *p, *q;
13591     int emptycount;
13592     ChessSquare piece;
13593
13594     whiteToPlay = (gameMode == EditPosition) ?
13595       !blackPlaysFirst : (move % 2 == 0);
13596     p = buf;
13597
13598     /* Piece placement data */
13599     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13600         emptycount = 0;
13601         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13602             if (boards[move][i][j] == EmptySquare) {
13603                 emptycount++;
13604             } else { ChessSquare piece = boards[move][i][j];
13605                 if (emptycount > 0) {
13606                     if(emptycount<10) /* [HGM] can be >= 10 */
13607                         *p++ = '0' + emptycount;
13608                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13609                     emptycount = 0;
13610                 }
13611                 if(PieceToChar(piece) == '+') {
13612                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13613                     *p++ = '+';
13614                     piece = (ChessSquare)(DEMOTED piece);
13615                 } 
13616                 *p++ = PieceToChar(piece);
13617                 if(p[-1] == '~') {
13618                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13619                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13620                     *p++ = '~';
13621                 }
13622             }
13623         }
13624         if (emptycount > 0) {
13625             if(emptycount<10) /* [HGM] can be >= 10 */
13626                 *p++ = '0' + emptycount;
13627             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13628             emptycount = 0;
13629         }
13630         *p++ = '/';
13631     }
13632     *(p - 1) = ' ';
13633
13634     /* [HGM] print Crazyhouse or Shogi holdings */
13635     if( gameInfo.holdingsWidth ) {
13636         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13637         q = p;
13638         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13639             piece = boards[move][i][BOARD_WIDTH-1];
13640             if( piece != EmptySquare )
13641               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13642                   *p++ = PieceToChar(piece);
13643         }
13644         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13645             piece = boards[move][BOARD_HEIGHT-i-1][0];
13646             if( piece != EmptySquare )
13647               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13648                   *p++ = PieceToChar(piece);
13649         }
13650
13651         if( q == p ) *p++ = '-';
13652         *p++ = ']';
13653         *p++ = ' ';
13654     }
13655
13656     /* Active color */
13657     *p++ = whiteToPlay ? 'w' : 'b';
13658     *p++ = ' ';
13659
13660   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13661     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13662   } else {
13663   if(nrCastlingRights) {
13664      q = p;
13665      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13666        /* [HGM] write directly from rights */
13667            if(castlingRights[move][2] >= 0 &&
13668               castlingRights[move][0] >= 0   )
13669                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13670            if(castlingRights[move][2] >= 0 &&
13671               castlingRights[move][1] >= 0   )
13672                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13673            if(castlingRights[move][5] >= 0 &&
13674               castlingRights[move][3] >= 0   )
13675                 *p++ = castlingRights[move][3] + AAA;
13676            if(castlingRights[move][5] >= 0 &&
13677               castlingRights[move][4] >= 0   )
13678                 *p++ = castlingRights[move][4] + AAA;
13679      } else {
13680
13681         /* [HGM] write true castling rights */
13682         if( nrCastlingRights == 6 ) {
13683             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13684                castlingRights[move][2] >= 0  ) *p++ = 'K';
13685             if(castlingRights[move][1] == BOARD_LEFT &&
13686                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13687             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13688                castlingRights[move][5] >= 0  ) *p++ = 'k';
13689             if(castlingRights[move][4] == BOARD_LEFT &&
13690                castlingRights[move][5] >= 0  ) *p++ = 'q';
13691         }
13692      }
13693      if (q == p) *p++ = '-'; /* No castling rights */
13694      *p++ = ' ';
13695   }
13696
13697   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13698      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13699     /* En passant target square */
13700     if (move > backwardMostMove) {
13701         fromX = moveList[move - 1][0] - AAA;
13702         fromY = moveList[move - 1][1] - ONE;
13703         toX = moveList[move - 1][2] - AAA;
13704         toY = moveList[move - 1][3] - ONE;
13705         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13706             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13707             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13708             fromX == toX) {
13709             /* 2-square pawn move just happened */
13710             *p++ = toX + AAA;
13711             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13712         } else {
13713             *p++ = '-';
13714         }
13715     } else if(move == backwardMostMove) {
13716         // [HGM] perhaps we should always do it like this, and forget the above?
13717         if(epStatus[move] >= 0) {
13718             *p++ = epStatus[move] + AAA;
13719             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13720         } else {
13721             *p++ = '-';
13722         }
13723     } else {
13724         *p++ = '-';
13725     }
13726     *p++ = ' ';
13727   }
13728   }
13729
13730     /* [HGM] find reversible plies */
13731     {   int i = 0, j=move;
13732
13733         if (appData.debugMode) { int k;
13734             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13735             for(k=backwardMostMove; k<=forwardMostMove; k++)
13736                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13737
13738         }
13739
13740         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13741         if( j == backwardMostMove ) i += initialRulePlies;
13742         sprintf(p, "%d ", i);
13743         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13744     }
13745     /* Fullmove number */
13746     sprintf(p, "%d", (move / 2) + 1);
13747     
13748     return StrSave(buf);
13749 }
13750
13751 Boolean
13752 ParseFEN(board, blackPlaysFirst, fen)
13753     Board board;
13754      int *blackPlaysFirst;
13755      char *fen;
13756 {
13757     int i, j;
13758     char *p;
13759     int emptycount;
13760     ChessSquare piece;
13761
13762     p = fen;
13763
13764     /* [HGM] by default clear Crazyhouse holdings, if present */
13765     if(gameInfo.holdingsWidth) {
13766        for(i=0; i<BOARD_HEIGHT; i++) {
13767            board[i][0]             = EmptySquare; /* black holdings */
13768            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13769            board[i][1]             = (ChessSquare) 0; /* black counts */
13770            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13771        }
13772     }
13773
13774     /* Piece placement data */
13775     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13776         j = 0;
13777         for (;;) {
13778             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13779                 if (*p == '/') p++;
13780                 emptycount = gameInfo.boardWidth - j;
13781                 while (emptycount--)
13782                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13783                 break;
13784 #if(BOARD_SIZE >= 10)
13785             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13786                 p++; emptycount=10;
13787                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13788                 while (emptycount--)
13789                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13790 #endif
13791             } else if (isdigit(*p)) {
13792                 emptycount = *p++ - '0';
13793                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13794                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13795                 while (emptycount--)
13796                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13797             } else if (*p == '+' || isalpha(*p)) {
13798                 if (j >= gameInfo.boardWidth) return FALSE;
13799                 if(*p=='+') {
13800                     piece = CharToPiece(*++p);
13801                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13802                     piece = (ChessSquare) (PROMOTED piece ); p++;
13803                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13804                 } else piece = CharToPiece(*p++);
13805
13806                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13807                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13808                     piece = (ChessSquare) (PROMOTED piece);
13809                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13810                     p++;
13811                 }
13812                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13813             } else {
13814                 return FALSE;
13815             }
13816         }
13817     }
13818     while (*p == '/' || *p == ' ') p++;
13819
13820     /* [HGM] look for Crazyhouse holdings here */
13821     while(*p==' ') p++;
13822     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13823         if(*p == '[') p++;
13824         if(*p == '-' ) *p++; /* empty holdings */ else {
13825             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13826             /* if we would allow FEN reading to set board size, we would   */
13827             /* have to add holdings and shift the board read so far here   */
13828             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13829                 *p++;
13830                 if((int) piece >= (int) BlackPawn ) {
13831                     i = (int)piece - (int)BlackPawn;
13832                     i = PieceToNumber((ChessSquare)i);
13833                     if( i >= gameInfo.holdingsSize ) return FALSE;
13834                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13835                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13836                 } else {
13837                     i = (int)piece - (int)WhitePawn;
13838                     i = PieceToNumber((ChessSquare)i);
13839                     if( i >= gameInfo.holdingsSize ) return FALSE;
13840                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13841                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13842                 }
13843             }
13844         }
13845         if(*p == ']') *p++;
13846     }
13847
13848     while(*p == ' ') p++;
13849
13850     /* Active color */
13851     switch (*p++) {
13852       case 'w':
13853         *blackPlaysFirst = FALSE;
13854         break;
13855       case 'b': 
13856         *blackPlaysFirst = TRUE;
13857         break;
13858       default:
13859         return FALSE;
13860     }
13861
13862     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13863     /* return the extra info in global variiables             */
13864
13865     /* set defaults in case FEN is incomplete */
13866     FENepStatus = EP_UNKNOWN;
13867     for(i=0; i<nrCastlingRights; i++ ) {
13868         FENcastlingRights[i] =
13869             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13870     }   /* assume possible unless obviously impossible */
13871     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13872     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13873     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13874     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13875     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13876     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13877     FENrulePlies = 0;
13878
13879     while(*p==' ') p++;
13880     if(nrCastlingRights) {
13881       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13882           /* castling indicator present, so default becomes no castlings */
13883           for(i=0; i<nrCastlingRights; i++ ) {
13884                  FENcastlingRights[i] = -1;
13885           }
13886       }
13887       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13888              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13889              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13890              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13891         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13892
13893         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13894             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13895             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13896         }
13897         switch(c) {
13898           case'K':
13899               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13900               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13901               FENcastlingRights[2] = whiteKingFile;
13902               break;
13903           case'Q':
13904               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13905               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13906               FENcastlingRights[2] = whiteKingFile;
13907               break;
13908           case'k':
13909               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13910               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13911               FENcastlingRights[5] = blackKingFile;
13912               break;
13913           case'q':
13914               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13915               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13916               FENcastlingRights[5] = blackKingFile;
13917           case '-':
13918               break;
13919           default: /* FRC castlings */
13920               if(c >= 'a') { /* black rights */
13921                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13922                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13923                   if(i == BOARD_RGHT) break;
13924                   FENcastlingRights[5] = i;
13925                   c -= AAA;
13926                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13927                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13928                   if(c > i)
13929                       FENcastlingRights[3] = c;
13930                   else
13931                       FENcastlingRights[4] = c;
13932               } else { /* white rights */
13933                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13934                     if(board[0][i] == WhiteKing) break;
13935                   if(i == BOARD_RGHT) break;
13936                   FENcastlingRights[2] = i;
13937                   c -= AAA - 'a' + 'A';
13938                   if(board[0][c] >= WhiteKing) break;
13939                   if(c > i)
13940                       FENcastlingRights[0] = c;
13941                   else
13942                       FENcastlingRights[1] = c;
13943               }
13944         }
13945       }
13946     if (appData.debugMode) {
13947         fprintf(debugFP, "FEN castling rights:");
13948         for(i=0; i<nrCastlingRights; i++)
13949         fprintf(debugFP, " %d", FENcastlingRights[i]);
13950         fprintf(debugFP, "\n");
13951     }
13952
13953       while(*p==' ') p++;
13954     }
13955
13956     /* read e.p. field in games that know e.p. capture */
13957     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13958        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13959       if(*p=='-') {
13960         p++; FENepStatus = EP_NONE;
13961       } else {
13962          char c = *p++ - AAA;
13963
13964          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13965          if(*p >= '0' && *p <='9') *p++;
13966          FENepStatus = c;
13967       }
13968     }
13969
13970
13971     if(sscanf(p, "%d", &i) == 1) {
13972         FENrulePlies = i; /* 50-move ply counter */
13973         /* (The move number is still ignored)    */
13974     }
13975
13976     return TRUE;
13977 }
13978       
13979 void
13980 EditPositionPasteFEN(char *fen)
13981 {
13982   if (fen != NULL) {
13983     Board initial_position;
13984
13985     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13986       DisplayError(_("Bad FEN position in clipboard"), 0);
13987       return ;
13988     } else {
13989       int savedBlackPlaysFirst = blackPlaysFirst;
13990       EditPositionEvent();
13991       blackPlaysFirst = savedBlackPlaysFirst;
13992       CopyBoard(boards[0], initial_position);
13993           /* [HGM] copy FEN attributes as well */
13994           {   int i;
13995               initialRulePlies = FENrulePlies;
13996               epStatus[0] = FENepStatus;
13997               for( i=0; i<nrCastlingRights; i++ )
13998                   castlingRights[0][i] = FENcastlingRights[i];
13999           }
14000       EditPositionDone();
14001       DisplayBothClocks();
14002       DrawPosition(FALSE, boards[currentMove]);
14003     }
14004   }
14005 }
14006
14007 static char cseq[12] = "\\   ";
14008
14009 Boolean set_cont_sequence(char *new_seq)
14010 {
14011     int len;
14012     Boolean ret;
14013
14014     // handle bad attempts to set the sequence
14015         if (!new_seq)
14016                 return 0; // acceptable error - no debug
14017
14018     len = strlen(new_seq);
14019     ret = (len > 0) && (len < sizeof(cseq));
14020     if (ret)
14021         strcpy(cseq, new_seq);
14022     else if (appData.debugMode)
14023         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14024     return ret;
14025 }
14026
14027 /*
14028     reformat a source message so words don't cross the width boundary.  internal
14029     newlines are not removed.  returns the wrapped size (no null character unless
14030     included in source message).  If dest is NULL, only calculate the size required
14031     for the dest buffer.  lp argument indicats line position upon entry, and it's
14032     passed back upon exit.
14033 */
14034 int wrap(char *dest, char *src, int count, int width, int *lp)
14035 {
14036     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14037
14038     cseq_len = strlen(cseq);
14039     old_line = line = *lp;
14040     ansi = len = clen = 0;
14041
14042     for (i=0; i < count; i++)
14043     {
14044         if (src[i] == '\033')
14045             ansi = 1;
14046
14047         // if we hit the width, back up
14048         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14049         {
14050             // store i & len in case the word is too long
14051             old_i = i, old_len = len;
14052
14053             // find the end of the last word
14054             while (i && src[i] != ' ' && src[i] != '\n')
14055             {
14056                 i--;
14057                 len--;
14058             }
14059
14060             // word too long?  restore i & len before splitting it
14061             if ((old_i-i+clen) >= width)
14062             {
14063                 i = old_i;
14064                 len = old_len;
14065             }
14066
14067             // extra space?
14068             if (i && src[i-1] == ' ')
14069                 len--;
14070
14071             if (src[i] != ' ' && src[i] != '\n')
14072             {
14073                 i--;
14074                 if (len)
14075                     len--;
14076             }
14077
14078             // now append the newline and continuation sequence
14079             if (dest)
14080                 dest[len] = '\n';
14081             len++;
14082             if (dest)
14083                 strncpy(dest+len, cseq, cseq_len);
14084             len += cseq_len;
14085             line = cseq_len;
14086             clen = cseq_len;
14087             continue;
14088         }
14089
14090         if (dest)
14091             dest[len] = src[i];
14092         len++;
14093         if (!ansi)
14094             line++;
14095         if (src[i] == '\n')
14096             line = 0;
14097         if (src[i] == 'm')
14098             ansi = 0;
14099     }
14100     if (dest && appData.debugMode)
14101     {
14102         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14103             count, width, line, len, *lp);
14104         show_bytes(debugFP, src, count);
14105         fprintf(debugFP, "\ndest: ");
14106         show_bytes(debugFP, dest, len);
14107         fprintf(debugFP, "\n");
14108     }
14109     *lp = dest ? line : old_line;
14110
14111     return len;
14112 }