fix crash on engine crash
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom: 
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /* 
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY       
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682     
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         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     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908         return; // prevent overwriting by pre-board holdings
1909
1910     if( (int)lowestPiece >= BlackPawn ) {
1911         holdingsColumn = 0;
1912         countsColumn = 1;
1913         holdingsStartRow = BOARD_HEIGHT-1;
1914         direction = -1;
1915     } else {
1916         holdingsColumn = BOARD_WIDTH-1;
1917         countsColumn = BOARD_WIDTH-2;
1918         holdingsStartRow = 0;
1919         direction = 1;
1920     }
1921
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923         board[i][holdingsColumn] = EmptySquare;
1924         board[i][countsColumn]   = (ChessSquare) 0;
1925     }
1926     while( (p=*holdings++) != NULLCHAR ) {
1927         piece = CharToPiece( ToUpper(p) );
1928         if(piece == EmptySquare) continue;
1929         /*j = (int) piece - (int) WhitePawn;*/
1930         j = PieceToNumber(piece);
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932         if(j < 0) continue;               /* should not happen */
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935         board[holdingsStartRow+j*direction][countsColumn]++;
1936     }
1937 }
1938
1939
1940 void
1941 VariantSwitch(Board board, VariantClass newVariant)
1942 {
1943    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944    Board oldBoard;
1945
1946    startedFromPositionFile = FALSE;
1947    if(gameInfo.variant == newVariant) return;
1948
1949    /* [HGM] This routine is called each time an assignment is made to
1950     * gameInfo.variant during a game, to make sure the board sizes
1951     * are set to match the new variant. If that means adding or deleting
1952     * holdings, we shift the playing board accordingly
1953     * This kludge is needed because in ICS observe mode, we get boards
1954     * of an ongoing game without knowing the variant, and learn about the
1955     * latter only later. This can be because of the move list we requested,
1956     * in which case the game history is refilled from the beginning anyway,
1957     * but also when receiving holdings of a crazyhouse game. In the latter
1958     * case we want to add those holdings to the already received position.
1959     */
1960
1961    
1962    if (appData.debugMode) {
1963      fprintf(debugFP, "Switch board from %s to %s\n",
1964              VariantName(gameInfo.variant), VariantName(newVariant));
1965      setbuf(debugFP, NULL);
1966    }
1967    shuffleOpenings = 0;       /* [HGM] shuffle */
1968    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969    switch(newVariant) 
1970      {
1971      case VariantShogi:
1972        newWidth = 9;  newHeight = 9;
1973        gameInfo.holdingsSize = 7;
1974      case VariantBughouse:
1975      case VariantCrazyhouse:
1976        newHoldingsWidth = 2; break;
1977      case VariantGreat:
1978        newWidth = 10;
1979      case VariantSuper:
1980        newHoldingsWidth = 2;
1981        gameInfo.holdingsSize = 8;
1982        break;
1983      case VariantGothic:
1984      case VariantCapablanca:
1985      case VariantCapaRandom:
1986        newWidth = 10;
1987      default:
1988        newHoldingsWidth = gameInfo.holdingsSize = 0;
1989      };
1990    
1991    if(newWidth  != gameInfo.boardWidth  ||
1992       newHeight != gameInfo.boardHeight ||
1993       newHoldingsWidth != gameInfo.holdingsWidth ) {
1994      
1995      /* shift position to new playing area, if needed */
1996      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997        for(i=0; i<BOARD_HEIGHT; i++) 
1998          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000              board[i][j];
2001        for(i=0; i<newHeight; i++) {
2002          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004        }
2005      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006        for(i=0; i<BOARD_HEIGHT; i++)
2007          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009              board[i][j];
2010      }
2011      gameInfo.boardWidth  = newWidth;
2012      gameInfo.boardHeight = newHeight;
2013      gameInfo.holdingsWidth = newHoldingsWidth;
2014      gameInfo.variant = newVariant;
2015      InitDrawingSizes(-2, 0);
2016    } else gameInfo.variant = newVariant;
2017    CopyBoard(oldBoard, board);   // remember correctly formatted board
2018      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2019    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2020 }
2021
2022 static int loggedOn = FALSE;
2023
2024 /*-- Game start info cache: --*/
2025 int gs_gamenum;
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\   ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2033
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2036
2037 void
2038 read_from_ics(isr, closure, data, count, error)
2039      InputSourceRef isr;
2040      VOIDSTAR closure;
2041      char *data;
2042      int count;
2043      int error;
2044 {
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2054     
2055     static int started = STARTED_NONE;
2056     static char parse[20000];
2057     static int parse_pos = 0;
2058     static char buf[BUF_SIZE + 1];
2059     static int firstTime = TRUE, intfSet = FALSE;
2060     static ColorClass prevColor = ColorNormal;
2061     static int savingComment = FALSE;
2062     static int cmatch = 0; // continuation sequence match
2063     char *bp;
2064     char str[500];
2065     int i, oldi;
2066     int buf_len;
2067     int next_out;
2068     int tkind;
2069     int backup;    /* [DM] For zippy color lines */
2070     char *p;
2071     char talker[MSG_SIZ]; // [HGM] chat
2072     int channel;
2073
2074     if (appData.debugMode) {
2075       if (!error) {
2076         fprintf(debugFP, "<ICS: ");
2077         show_bytes(debugFP, data, count);
2078         fprintf(debugFP, "\n");
2079       }
2080     }
2081
2082     if (appData.debugMode) { int f = forwardMostMove;
2083         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085     }
2086     if (count > 0) {
2087         /* If last read ended with a partial line that we couldn't parse,
2088            prepend it to the new read and try again. */
2089         if (leftover_len > 0) {
2090             for (i=0; i<leftover_len; i++)
2091               buf[i] = buf[leftover_start + i];
2092         }
2093
2094     /* copy new characters into the buffer */
2095     bp = buf + leftover_len;
2096     buf_len=leftover_len;
2097     for (i=0; i<count; i++)
2098     {
2099         // ignore these
2100         if (data[i] == '\r')
2101             continue;
2102
2103         // join lines split by ICS?
2104         if (!appData.noJoin)
2105         {
2106             /*
2107                 Joining just consists of finding matches against the
2108                 continuation sequence, and discarding that sequence
2109                 if found instead of copying it.  So, until a match
2110                 fails, there's nothing to do since it might be the
2111                 complete sequence, and thus, something we don't want
2112                 copied.
2113             */
2114             if (data[i] == cont_seq[cmatch])
2115             {
2116                 cmatch++;
2117                 if (cmatch == strlen(cont_seq))
2118                 {
2119                     cmatch = 0; // complete match.  just reset the counter
2120
2121                     /*
2122                         it's possible for the ICS to not include the space
2123                         at the end of the last word, making our [correct]
2124                         join operation fuse two separate words.  the server
2125                         does this when the space occurs at the width setting.
2126                     */
2127                     if (!buf_len || buf[buf_len-1] != ' ')
2128                     {
2129                         *bp++ = ' ';
2130                         buf_len++;
2131                     }
2132                 }
2133                 continue;
2134             }
2135             else if (cmatch)
2136             {
2137                 /*
2138                     match failed, so we have to copy what matched before
2139                     falling through and copying this character.  In reality,
2140                     this will only ever be just the newline character, but
2141                     it doesn't hurt to be precise.
2142                 */
2143                 strncpy(bp, cont_seq, cmatch);
2144                 bp += cmatch;
2145                 buf_len += cmatch;
2146                 cmatch = 0;
2147             }
2148         }
2149
2150         // copy this char
2151         *bp++ = data[i];
2152         buf_len++;
2153     }
2154
2155         buf[buf_len] = NULLCHAR;
2156         next_out = leftover_len;
2157         leftover_start = 0;
2158         
2159         i = 0;
2160         while (i < buf_len) {
2161             /* Deal with part of the TELNET option negotiation
2162                protocol.  We refuse to do anything beyond the
2163                defaults, except that we allow the WILL ECHO option,
2164                which ICS uses to turn off password echoing when we are
2165                directly connected to it.  We reject this option
2166                if localLineEditing mode is on (always on in xboard)
2167                and we are talking to port 23, which might be a real
2168                telnet server that will try to keep WILL ECHO on permanently.
2169              */
2170             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172                 unsigned char option;
2173                 oldi = i;
2174                 switch ((unsigned char) buf[++i]) {
2175                   case TN_WILL:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<WILL ");
2178                     switch (option = (unsigned char) buf[++i]) {
2179                       case TN_ECHO:
2180                         if (appData.debugMode)
2181                           fprintf(debugFP, "ECHO ");
2182                         /* Reply only if this is a change, according
2183                            to the protocol rules. */
2184                         if (remoteEchoOption) break;
2185                         if (appData.localLineEditing &&
2186                             atoi(appData.icsPort) == TN_PORT) {
2187                             TelnetRequest(TN_DONT, TN_ECHO);
2188                         } else {
2189                             EchoOff();
2190                             TelnetRequest(TN_DO, TN_ECHO);
2191                             remoteEchoOption = TRUE;
2192                         }
2193                         break;
2194                       default:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "%d ", option);
2197                         /* Whatever this is, we don't want it. */
2198                         TelnetRequest(TN_DONT, option);
2199                         break;
2200                     }
2201                     break;
2202                   case TN_WONT:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<WONT ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       case TN_ECHO:
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "ECHO ");
2209                         /* Reply only if this is a change, according
2210                            to the protocol rules. */
2211                         if (!remoteEchoOption) break;
2212                         EchoOn();
2213                         TelnetRequest(TN_DONT, TN_ECHO);
2214                         remoteEchoOption = FALSE;
2215                         break;
2216                       default:
2217                         if (appData.debugMode)
2218                           fprintf(debugFP, "%d ", (unsigned char) option);
2219                         /* Whatever this is, it must already be turned
2220                            off, because we never agree to turn on
2221                            anything non-default, so according to the
2222                            protocol rules, we don't reply. */
2223                         break;
2224                     }
2225                     break;
2226                   case TN_DO:
2227                     if (appData.debugMode)
2228                       fprintf(debugFP, "\n<DO ");
2229                     switch (option = (unsigned char) buf[++i]) {
2230                       default:
2231                         /* Whatever this is, we refuse to do it. */
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", option);
2234                         TelnetRequest(TN_WONT, option);
2235                         break;
2236                     }
2237                     break;
2238                   case TN_DONT:
2239                     if (appData.debugMode)
2240                       fprintf(debugFP, "\n<DONT ");
2241                     switch (option = (unsigned char) buf[++i]) {
2242                       default:
2243                         if (appData.debugMode)
2244                           fprintf(debugFP, "%d ", option);
2245                         /* Whatever this is, we are already not doing
2246                            it, because we never agree to do anything
2247                            non-default, so according to the protocol
2248                            rules, we don't reply. */
2249                         break;
2250                     }
2251                     break;
2252                   case TN_IAC:
2253                     if (appData.debugMode)
2254                       fprintf(debugFP, "\n<IAC ");
2255                     /* Doubled IAC; pass it through */
2256                     i--;
2257                     break;
2258                   default:
2259                     if (appData.debugMode)
2260                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261                     /* Drop all other telnet commands on the floor */
2262                     break;
2263                 }
2264                 if (oldi > next_out)
2265                   SendToPlayer(&buf[next_out], oldi - next_out);
2266                 if (++i > next_out)
2267                   next_out = i;
2268                 continue;
2269             }
2270                 
2271             /* OK, this at least will *usually* work */
2272             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2273                 loggedOn = TRUE;
2274             }
2275             
2276             if (loggedOn && !intfSet) {
2277                 if (ics_type == ICS_ICC) {
2278                   sprintf(str,
2279                           "/set-quietly interface %s\n/set-quietly style 12\n",
2280                           programVersion);
2281                 } else if (ics_type == ICS_CHESSNET) {
2282                   sprintf(str, "/style 12\n");
2283                 } else {
2284                   strcpy(str, "alias $ @\n$set interface ");
2285                   strcat(str, programVersion);
2286                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2287 #ifdef WIN32
2288                   strcat(str, "$iset nohighlight 1\n");
2289 #endif
2290                   strcat(str, "$iset lock 1\n$style 12\n");
2291                 }
2292                 SendToICS(str);
2293                 NotifyFrontendLogin();
2294                 intfSet = TRUE;
2295             }
2296
2297             if (started == STARTED_COMMENT) {
2298                 /* Accumulate characters in comment */
2299                 parse[parse_pos++] = buf[i];
2300                 if (buf[i] == '\n') {
2301                     parse[parse_pos] = NULLCHAR;
2302                     if(chattingPartner>=0) {
2303                         char mess[MSG_SIZ];
2304                         sprintf(mess, "%s%s", talker, parse);
2305                         OutputChatMessage(chattingPartner, mess);
2306                         chattingPartner = -1;
2307                     } else
2308                     if(!suppressKibitz) // [HGM] kibitz
2309                         AppendComment(forwardMostMove, StripHighlight(parse));
2310                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311                         int nrDigit = 0, nrAlph = 0, i;
2312                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314                         parse[parse_pos] = NULLCHAR;
2315                         // try to be smart: if it does not look like search info, it should go to
2316                         // ICS interaction window after all, not to engine-output window.
2317                         for(i=0; i<parse_pos; i++) { // count letters and digits
2318                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2320                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2321                         }
2322                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323                             int depth=0; float score;
2324                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326                                 pvInfoList[forwardMostMove-1].depth = depth;
2327                                 pvInfoList[forwardMostMove-1].score = 100*score;
2328                             }
2329                             OutputKibitz(suppressKibitz, parse);
2330                         } else {
2331                             char tmp[MSG_SIZ];
2332                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333                             SendToPlayer(tmp, strlen(tmp));
2334                         }
2335                     }
2336                     started = STARTED_NONE;
2337                 } else {
2338                     /* Don't match patterns against characters in chatter */
2339                     i++;
2340                     continue;
2341                 }
2342             }
2343             if (started == STARTED_CHATTER) {
2344                 if (buf[i] != '\n') {
2345                     /* Don't match patterns against characters in chatter */
2346                     i++;
2347                     continue;
2348                 }
2349                 started = STARTED_NONE;
2350             }
2351
2352             /* Kludge to deal with rcmd protocol */
2353             if (firstTime && looking_at(buf, &i, "\001*")) {
2354                 DisplayFatalError(&buf[1], 0, 1);
2355                 continue;
2356             } else {
2357                 firstTime = FALSE;
2358             }
2359
2360             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361                 ics_type = ICS_ICC;
2362                 ics_prefix = "/";
2363                 if (appData.debugMode)
2364                   fprintf(debugFP, "ics_type %d\n", ics_type);
2365                 continue;
2366             }
2367             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368                 ics_type = ICS_FICS;
2369                 ics_prefix = "$";
2370                 if (appData.debugMode)
2371                   fprintf(debugFP, "ics_type %d\n", ics_type);
2372                 continue;
2373             }
2374             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375                 ics_type = ICS_CHESSNET;
2376                 ics_prefix = "/";
2377                 if (appData.debugMode)
2378                   fprintf(debugFP, "ics_type %d\n", ics_type);
2379                 continue;
2380             }
2381
2382             if (!loggedOn &&
2383                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2385                  looking_at(buf, &i, "will be \"*\""))) {
2386               strcpy(ics_handle, star_match[0]);
2387               continue;
2388             }
2389
2390             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2391               char buf[MSG_SIZ];
2392               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393               DisplayIcsInteractionTitle(buf);
2394               have_set_title = TRUE;
2395             }
2396
2397             /* skip finger notes */
2398             if (started == STARTED_NONE &&
2399                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400                  (buf[i] == '1' && buf[i+1] == '0')) &&
2401                 buf[i+2] == ':' && buf[i+3] == ' ') {
2402               started = STARTED_CHATTER;
2403               i += 3;
2404               continue;
2405             }
2406
2407             /* skip formula vars */
2408             if (started == STARTED_NONE &&
2409                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410               started = STARTED_CHATTER;
2411               i += 3;
2412               continue;
2413             }
2414
2415             oldi = i;
2416             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417             if (appData.autoKibitz && started == STARTED_NONE && 
2418                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2419                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420                 if(looking_at(buf, &i, "* kibitzes: ") &&
2421                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2422                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2423                         suppressKibitz = TRUE;
2424                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425                                 && (gameMode == IcsPlayingWhite)) ||
2426                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2428                             started = STARTED_CHATTER; // own kibitz we simply discard
2429                         else {
2430                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431                             parse_pos = 0; parse[0] = NULLCHAR;
2432                             savingComment = TRUE;
2433                             suppressKibitz = gameMode != IcsObserving ? 2 :
2434                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2435                         } 
2436                         continue;
2437                 } else
2438                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439                     started = STARTED_CHATTER;
2440                     suppressKibitz = TRUE;
2441                 }
2442             } // [HGM] kibitz: end of patch
2443
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2445
2446             // [HGM] chat: intercept tells by users for which we have an open chat window
2447             channel = -1;
2448             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2449                                            looking_at(buf, &i, "* whispers:") ||
2450                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2452                 int p;
2453                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454                 chattingPartner = -1;
2455
2456                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457                 for(p=0; p<MAX_CHAT; p++) {
2458                     if(channel == atoi(chatPartner[p])) {
2459                     talker[0] = '['; strcat(talker, "]");
2460                     chattingPartner = p; break;
2461                     }
2462                 } else
2463                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464                 for(p=0; p<MAX_CHAT; p++) {
2465                     if(!strcmp("WHISPER", chatPartner[p])) {
2466                         talker[0] = '['; strcat(talker, "]");
2467                         chattingPartner = p; break;
2468                     }
2469                 }
2470                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2472                     talker[0] = 0;
2473                     chattingPartner = p; break;
2474                 }
2475                 if(chattingPartner<0) i = oldi; else {
2476                     started = STARTED_COMMENT;
2477                     parse_pos = 0; parse[0] = NULLCHAR;
2478                     savingComment = TRUE;
2479                     suppressKibitz = TRUE;
2480                 }
2481             } // [HGM] chat: end of patch
2482
2483             if (appData.zippyTalk || appData.zippyPlay) {
2484                 /* [DM] Backup address for color zippy lines */
2485                 backup = i;
2486 #if ZIPPY
2487        #ifdef WIN32
2488                if (loggedOn == TRUE)
2489                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2491        #else
2492                 if (ZippyControl(buf, &i) ||
2493                     ZippyConverse(buf, &i) ||
2494                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2495                       loggedOn = TRUE;
2496                       if (!appData.colorize) continue;
2497                 }
2498        #endif
2499 #endif
2500             } // [DM] 'else { ' deleted
2501                 if (
2502                     /* Regular tells and says */
2503                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2505                     looking_at(buf, &i, "* says: ") ||
2506                     /* Don't color "message" or "messages" output */
2507                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508                     looking_at(buf, &i, "*. * at *:*: ") ||
2509                     looking_at(buf, &i, "--* (*:*): ") ||
2510                     /* Message notifications (same color as tells) */
2511                     looking_at(buf, &i, "* has left a message ") ||
2512                     looking_at(buf, &i, "* just sent you a message:\n") ||
2513                     /* Whispers and kibitzes */
2514                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515                     looking_at(buf, &i, "* kibitzes: ") ||
2516                     /* Channel tells */
2517                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2518
2519                   if (tkind == 1 && strchr(star_match[0], ':')) {
2520                       /* Avoid "tells you:" spoofs in channels */
2521                      tkind = 3;
2522                   }
2523                   if (star_match[0][0] == NULLCHAR ||
2524                       strchr(star_match[0], ' ') ||
2525                       (tkind == 3 && strchr(star_match[1], ' '))) {
2526                     /* Reject bogus matches */
2527                     i = oldi;
2528                   } else {
2529                     if (appData.colorize) {
2530                       if (oldi > next_out) {
2531                         SendToPlayer(&buf[next_out], oldi - next_out);
2532                         next_out = oldi;
2533                       }
2534                       switch (tkind) {
2535                       case 1:
2536                         Colorize(ColorTell, FALSE);
2537                         curColor = ColorTell;
2538                         break;
2539                       case 2:
2540                         Colorize(ColorKibitz, FALSE);
2541                         curColor = ColorKibitz;
2542                         break;
2543                       case 3:
2544                         p = strrchr(star_match[1], '(');
2545                         if (p == NULL) {
2546                           p = star_match[1];
2547                         } else {
2548                           p++;
2549                         }
2550                         if (atoi(p) == 1) {
2551                           Colorize(ColorChannel1, FALSE);
2552                           curColor = ColorChannel1;
2553                         } else {
2554                           Colorize(ColorChannel, FALSE);
2555                           curColor = ColorChannel;
2556                         }
2557                         break;
2558                       case 5:
2559                         curColor = ColorNormal;
2560                         break;
2561                       }
2562                     }
2563                     if (started == STARTED_NONE && appData.autoComment &&
2564                         (gameMode == IcsObserving ||
2565                          gameMode == IcsPlayingWhite ||
2566                          gameMode == IcsPlayingBlack)) {
2567                       parse_pos = i - oldi;
2568                       memcpy(parse, &buf[oldi], parse_pos);
2569                       parse[parse_pos] = NULLCHAR;
2570                       started = STARTED_COMMENT;
2571                       savingComment = TRUE;
2572                     } else {
2573                       started = STARTED_CHATTER;
2574                       savingComment = FALSE;
2575                     }
2576                     loggedOn = TRUE;
2577                     continue;
2578                   }
2579                 }
2580
2581                 if (looking_at(buf, &i, "* s-shouts: ") ||
2582                     looking_at(buf, &i, "* c-shouts: ")) {
2583                     if (appData.colorize) {
2584                         if (oldi > next_out) {
2585                             SendToPlayer(&buf[next_out], oldi - next_out);
2586                             next_out = oldi;
2587                         }
2588                         Colorize(ColorSShout, FALSE);
2589                         curColor = ColorSShout;
2590                     }
2591                     loggedOn = TRUE;
2592                     started = STARTED_CHATTER;
2593                     continue;
2594                 }
2595
2596                 if (looking_at(buf, &i, "--->")) {
2597                     loggedOn = TRUE;
2598                     continue;
2599                 }
2600
2601                 if (looking_at(buf, &i, "* shouts: ") ||
2602                     looking_at(buf, &i, "--> ")) {
2603                     if (appData.colorize) {
2604                         if (oldi > next_out) {
2605                             SendToPlayer(&buf[next_out], oldi - next_out);
2606                             next_out = oldi;
2607                         }
2608                         Colorize(ColorShout, FALSE);
2609                         curColor = ColorShout;
2610                     }
2611                     loggedOn = TRUE;
2612                     started = STARTED_CHATTER;
2613                     continue;
2614                 }
2615
2616                 if (looking_at( buf, &i, "Challenge:")) {
2617                     if (appData.colorize) {
2618                         if (oldi > next_out) {
2619                             SendToPlayer(&buf[next_out], oldi - next_out);
2620                             next_out = oldi;
2621                         }
2622                         Colorize(ColorChallenge, FALSE);
2623                         curColor = ColorChallenge;
2624                     }
2625                     loggedOn = TRUE;
2626                     continue;
2627                 }
2628
2629                 if (looking_at(buf, &i, "* offers you") ||
2630                     looking_at(buf, &i, "* offers to be") ||
2631                     looking_at(buf, &i, "* would like to") ||
2632                     looking_at(buf, &i, "* requests to") ||
2633                     looking_at(buf, &i, "Your opponent offers") ||
2634                     looking_at(buf, &i, "Your opponent requests")) {
2635
2636                     if (appData.colorize) {
2637                         if (oldi > next_out) {
2638                             SendToPlayer(&buf[next_out], oldi - next_out);
2639                             next_out = oldi;
2640                         }
2641                         Colorize(ColorRequest, FALSE);
2642                         curColor = ColorRequest;
2643                     }
2644                     continue;
2645                 }
2646
2647                 if (looking_at(buf, &i, "* (*) seeking")) {
2648                     if (appData.colorize) {
2649                         if (oldi > next_out) {
2650                             SendToPlayer(&buf[next_out], oldi - next_out);
2651                             next_out = oldi;
2652                         }
2653                         Colorize(ColorSeek, FALSE);
2654                         curColor = ColorSeek;
2655                     }
2656                     continue;
2657             }
2658
2659             if (looking_at(buf, &i, "\\   ")) {
2660                 if (prevColor != ColorNormal) {
2661                     if (oldi > next_out) {
2662                         SendToPlayer(&buf[next_out], oldi - next_out);
2663                         next_out = oldi;
2664                     }
2665                     Colorize(prevColor, TRUE);
2666                     curColor = prevColor;
2667                 }
2668                 if (savingComment) {
2669                     parse_pos = i - oldi;
2670                     memcpy(parse, &buf[oldi], parse_pos);
2671                     parse[parse_pos] = NULLCHAR;
2672                     started = STARTED_COMMENT;
2673                 } else {
2674                     started = STARTED_CHATTER;
2675                 }
2676                 continue;
2677             }
2678
2679             if (looking_at(buf, &i, "Black Strength :") ||
2680                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681                 looking_at(buf, &i, "<10>") ||
2682                 looking_at(buf, &i, "#@#")) {
2683                 /* Wrong board style */
2684                 loggedOn = TRUE;
2685                 SendToICS(ics_prefix);
2686                 SendToICS("set style 12\n");
2687                 SendToICS(ics_prefix);
2688                 SendToICS("refresh\n");
2689                 continue;
2690             }
2691             
2692             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2693                 ICSInitScript();
2694                 have_sent_ICS_logon = 1;
2695                 continue;
2696             }
2697               
2698             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2699                 (looking_at(buf, &i, "\n<12> ") ||
2700                  looking_at(buf, &i, "<12> "))) {
2701                 loggedOn = TRUE;
2702                 if (oldi > next_out) {
2703                     SendToPlayer(&buf[next_out], oldi - next_out);
2704                 }
2705                 next_out = i;
2706                 started = STARTED_BOARD;
2707                 parse_pos = 0;
2708                 continue;
2709             }
2710
2711             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712                 looking_at(buf, &i, "<b1> ")) {
2713                 if (oldi > next_out) {
2714                     SendToPlayer(&buf[next_out], oldi - next_out);
2715                 }
2716                 next_out = i;
2717                 started = STARTED_HOLDINGS;
2718                 parse_pos = 0;
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2723                 loggedOn = TRUE;
2724                 /* Header for a move list -- first line */
2725
2726                 switch (ics_getting_history) {
2727                   case H_FALSE:
2728                     switch (gameMode) {
2729                       case IcsIdle:
2730                       case BeginningOfGame:
2731                         /* User typed "moves" or "oldmoves" while we
2732                            were idle.  Pretend we asked for these
2733                            moves and soak them up so user can step
2734                            through them and/or save them.
2735                            */
2736                         Reset(FALSE, TRUE);
2737                         gameMode = IcsObserving;
2738                         ModeHighlight();
2739                         ics_gamenum = -1;
2740                         ics_getting_history = H_GOT_UNREQ_HEADER;
2741                         break;
2742                       case EditGame: /*?*/
2743                       case EditPosition: /*?*/
2744                         /* Should above feature work in these modes too? */
2745                         /* For now it doesn't */
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                       default:
2749                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2750                         break;
2751                     }
2752                     break;
2753                   case H_REQUESTED:
2754                     /* Is this the right one? */
2755                     if (gameInfo.white && gameInfo.black &&
2756                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2757                         strcmp(gameInfo.black, star_match[2]) == 0) {
2758                         /* All is well */
2759                         ics_getting_history = H_GOT_REQ_HEADER;
2760                     }
2761                     break;
2762                   case H_GOT_REQ_HEADER:
2763                   case H_GOT_UNREQ_HEADER:
2764                   case H_GOT_UNWANTED_HEADER:
2765                   case H_GETTING_MOVES:
2766                     /* Should not happen */
2767                     DisplayError(_("Error gathering move list: two headers"), 0);
2768                     ics_getting_history = H_FALSE;
2769                     break;
2770                 }
2771
2772                 /* Save player ratings into gameInfo if needed */
2773                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775                     (gameInfo.whiteRating == -1 ||
2776                      gameInfo.blackRating == -1)) {
2777
2778                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2779                     gameInfo.blackRating = string_to_rating(star_match[3]);
2780                     if (appData.debugMode)
2781                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2782                               gameInfo.whiteRating, gameInfo.blackRating);
2783                 }
2784                 continue;
2785             }
2786
2787             if (looking_at(buf, &i,
2788               "* * match, initial time: * minute*, increment: * second")) {
2789                 /* Header for a move list -- second line */
2790                 /* Initial board will follow if this is a wild game */
2791                 if (gameInfo.event != NULL) free(gameInfo.event);
2792                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793                 gameInfo.event = StrSave(str);
2794                 /* [HGM] we switched variant. Translate boards if needed. */
2795                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2796                 continue;
2797             }
2798
2799             if (looking_at(buf, &i, "Move  ")) {
2800                 /* Beginning of a move list */
2801                 switch (ics_getting_history) {
2802                   case H_FALSE:
2803                     /* Normally should not happen */
2804                     /* Maybe user hit reset while we were parsing */
2805                     break;
2806                   case H_REQUESTED:
2807                     /* Happens if we are ignoring a move list that is not
2808                      * the one we just requested.  Common if the user
2809                      * tries to observe two games without turning off
2810                      * getMoveList */
2811                     break;
2812                   case H_GETTING_MOVES:
2813                     /* Should not happen */
2814                     DisplayError(_("Error gathering move list: nested"), 0);
2815                     ics_getting_history = H_FALSE;
2816                     break;
2817                   case H_GOT_REQ_HEADER:
2818                     ics_getting_history = H_GETTING_MOVES;
2819                     started = STARTED_MOVES;
2820                     parse_pos = 0;
2821                     if (oldi > next_out) {
2822                         SendToPlayer(&buf[next_out], oldi - next_out);
2823                     }
2824                     break;
2825                   case H_GOT_UNREQ_HEADER:
2826                     ics_getting_history = H_GETTING_MOVES;
2827                     started = STARTED_MOVES_NOHIDE;
2828                     parse_pos = 0;
2829                     break;
2830                   case H_GOT_UNWANTED_HEADER:
2831                     ics_getting_history = H_FALSE;
2832                     break;
2833                 }
2834                 continue;
2835             }                           
2836             
2837             if (looking_at(buf, &i, "% ") ||
2838                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840                 savingComment = FALSE;
2841                 switch (started) {
2842                   case STARTED_MOVES:
2843                   case STARTED_MOVES_NOHIDE:
2844                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845                     parse[parse_pos + i - oldi] = NULLCHAR;
2846                     ParseGameHistory(parse);
2847 #if ZIPPY
2848                     if (appData.zippyPlay && first.initDone) {
2849                         FeedMovesToProgram(&first, forwardMostMove);
2850                         if (gameMode == IcsPlayingWhite) {
2851                             if (WhiteOnMove(forwardMostMove)) {
2852                                 if (first.sendTime) {
2853                                   if (first.useColors) {
2854                                     SendToProgram("black\n", &first); 
2855                                   }
2856                                   SendTimeRemaining(&first, TRUE);
2857                                 }
2858                                 if (first.useColors) {
2859                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2860                                 }
2861                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862                                 first.maybeThinking = TRUE;
2863                             } else {
2864                                 if (first.usePlayother) {
2865                                   if (first.sendTime) {
2866                                     SendTimeRemaining(&first, TRUE);
2867                                   }
2868                                   SendToProgram("playother\n", &first);
2869                                   firstMove = FALSE;
2870                                 } else {
2871                                   firstMove = TRUE;
2872                                 }
2873                             }
2874                         } else if (gameMode == IcsPlayingBlack) {
2875                             if (!WhiteOnMove(forwardMostMove)) {
2876                                 if (first.sendTime) {
2877                                   if (first.useColors) {
2878                                     SendToProgram("white\n", &first);
2879                                   }
2880                                   SendTimeRemaining(&first, FALSE);
2881                                 }
2882                                 if (first.useColors) {
2883                                   SendToProgram("black\n", &first);
2884                                 }
2885                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886                                 first.maybeThinking = TRUE;
2887                             } else {
2888                                 if (first.usePlayother) {
2889                                   if (first.sendTime) {
2890                                     SendTimeRemaining(&first, FALSE);
2891                                   }
2892                                   SendToProgram("playother\n", &first);
2893                                   firstMove = FALSE;
2894                                 } else {
2895                                   firstMove = TRUE;
2896                                 }
2897                             }
2898                         }                       
2899                     }
2900 #endif
2901                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2902                         /* Moves came from oldmoves or moves command
2903                            while we weren't doing anything else.
2904                            */
2905                         currentMove = forwardMostMove;
2906                         ClearHighlights();/*!!could figure this out*/
2907                         flipView = appData.flipView;
2908                         DrawPosition(TRUE, boards[currentMove]);
2909                         DisplayBothClocks();
2910                         sprintf(str, "%s vs. %s",
2911                                 gameInfo.white, gameInfo.black);
2912                         DisplayTitle(str);
2913                         gameMode = IcsIdle;
2914                     } else {
2915                         /* Moves were history of an active game */
2916                         if (gameInfo.resultDetails != NULL) {
2917                             free(gameInfo.resultDetails);
2918                             gameInfo.resultDetails = NULL;
2919                         }
2920                     }
2921                     HistorySet(parseList, backwardMostMove,
2922                                forwardMostMove, currentMove-1);
2923                     DisplayMove(currentMove - 1);
2924                     if (started == STARTED_MOVES) next_out = i;
2925                     started = STARTED_NONE;
2926                     ics_getting_history = H_FALSE;
2927                     break;
2928
2929                   case STARTED_OBSERVE:
2930                     started = STARTED_NONE;
2931                     SendToICS(ics_prefix);
2932                     SendToICS("refresh\n");
2933                     break;
2934
2935                   default:
2936                     break;
2937                 }
2938                 if(bookHit) { // [HGM] book: simulate book reply
2939                     static char bookMove[MSG_SIZ]; // a bit generous?
2940
2941                     programStats.nodes = programStats.depth = programStats.time = 
2942                     programStats.score = programStats.got_only_move = 0;
2943                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2944
2945                     strcpy(bookMove, "move ");
2946                     strcat(bookMove, bookHit);
2947                     HandleMachineMove(bookMove, &first);
2948                 }
2949                 continue;
2950             }
2951             
2952             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953                  started == STARTED_HOLDINGS ||
2954                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955                 /* Accumulate characters in move list or board */
2956                 parse[parse_pos++] = buf[i];
2957             }
2958             
2959             /* Start of game messages.  Mostly we detect start of game
2960                when the first board image arrives.  On some versions
2961                of the ICS, though, we need to do a "refresh" after starting
2962                to observe in order to get the current board right away. */
2963             if (looking_at(buf, &i, "Adding game * to observation list")) {
2964                 started = STARTED_OBSERVE;
2965                 continue;
2966             }
2967
2968             /* Handle auto-observe */
2969             if (appData.autoObserve &&
2970                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2972                 char *player;
2973                 /* Choose the player that was highlighted, if any. */
2974                 if (star_match[0][0] == '\033' ||
2975                     star_match[1][0] != '\033') {
2976                     player = star_match[0];
2977                 } else {
2978                     player = star_match[2];
2979                 }
2980                 sprintf(str, "%sobserve %s\n",
2981                         ics_prefix, StripHighlightAndTitle(player));
2982                 SendToICS(str);
2983
2984                 /* Save ratings from notify string */
2985                 strcpy(player1Name, star_match[0]);
2986                 player1Rating = string_to_rating(star_match[1]);
2987                 strcpy(player2Name, star_match[2]);
2988                 player2Rating = string_to_rating(star_match[3]);
2989
2990                 if (appData.debugMode)
2991                   fprintf(debugFP, 
2992                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2993                           player1Name, player1Rating,
2994                           player2Name, player2Rating);
2995
2996                 continue;
2997             }
2998
2999             /* Deal with automatic examine mode after a game,
3000                and with IcsObserving -> IcsExamining transition */
3001             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002                 looking_at(buf, &i, "has made you an examiner of game *")) {
3003
3004                 int gamenum = atoi(star_match[0]);
3005                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006                     gamenum == ics_gamenum) {
3007                     /* We were already playing or observing this game;
3008                        no need to refetch history */
3009                     gameMode = IcsExamining;
3010                     if (pausing) {
3011                         pauseExamForwardMostMove = forwardMostMove;
3012                     } else if (currentMove < forwardMostMove) {
3013                         ForwardInner(forwardMostMove);
3014                     }
3015                 } else {
3016                     /* I don't think this case really can happen */
3017                     SendToICS(ics_prefix);
3018                     SendToICS("refresh\n");
3019                 }
3020                 continue;
3021             }    
3022             
3023             /* Error messages */
3024 //          if (ics_user_moved) {
3025             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026                 if (looking_at(buf, &i, "Illegal move") ||
3027                     looking_at(buf, &i, "Not a legal move") ||
3028                     looking_at(buf, &i, "Your king is in check") ||
3029                     looking_at(buf, &i, "It isn't your turn") ||
3030                     looking_at(buf, &i, "It is not your move")) {
3031                     /* Illegal move */
3032                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033                         currentMove = --forwardMostMove;
3034                         DisplayMove(currentMove - 1); /* before DMError */
3035                         DrawPosition(FALSE, boards[currentMove]);
3036                         SwitchClocks();
3037                         DisplayBothClocks();
3038                     }
3039                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3040                     ics_user_moved = 0;
3041                     continue;
3042                 }
3043             }
3044
3045             if (looking_at(buf, &i, "still have time") ||
3046                 looking_at(buf, &i, "not out of time") ||
3047                 looking_at(buf, &i, "either player is out of time") ||
3048                 looking_at(buf, &i, "has timeseal; checking")) {
3049                 /* We must have called his flag a little too soon */
3050                 whiteFlag = blackFlag = FALSE;
3051                 continue;
3052             }
3053
3054             if (looking_at(buf, &i, "added * seconds to") ||
3055                 looking_at(buf, &i, "seconds were added to")) {
3056                 /* Update the clocks */
3057                 SendToICS(ics_prefix);
3058                 SendToICS("refresh\n");
3059                 continue;
3060             }
3061
3062             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063                 ics_clock_paused = TRUE;
3064                 StopClocks();
3065                 continue;
3066             }
3067
3068             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069                 ics_clock_paused = FALSE;
3070                 StartClocks();
3071                 continue;
3072             }
3073
3074             /* Grab player ratings from the Creating: message.
3075                Note we have to check for the special case when
3076                the ICS inserts things like [white] or [black]. */
3077             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3079                 /* star_matches:
3080                    0    player 1 name (not necessarily white)
3081                    1    player 1 rating
3082                    2    empty, white, or black (IGNORED)
3083                    3    player 2 name (not necessarily black)
3084                    4    player 2 rating
3085                    
3086                    The names/ratings are sorted out when the game
3087                    actually starts (below).
3088                 */
3089                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090                 player1Rating = string_to_rating(star_match[1]);
3091                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092                 player2Rating = string_to_rating(star_match[4]);
3093
3094                 if (appData.debugMode)
3095                   fprintf(debugFP, 
3096                           "Ratings from 'Creating:' %s %d, %s %d\n",
3097                           player1Name, player1Rating,
3098                           player2Name, player2Rating);
3099
3100                 continue;
3101             }
3102             
3103             /* Improved generic start/end-of-game messages */
3104             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106                 /* If tkind == 0: */
3107                 /* star_match[0] is the game number */
3108                 /*           [1] is the white player's name */
3109                 /*           [2] is the black player's name */
3110                 /* For end-of-game: */
3111                 /*           [3] is the reason for the game end */
3112                 /*           [4] is a PGN end game-token, preceded by " " */
3113                 /* For start-of-game: */
3114                 /*           [3] begins with "Creating" or "Continuing" */
3115                 /*           [4] is " *" or empty (don't care). */
3116                 int gamenum = atoi(star_match[0]);
3117                 char *whitename, *blackname, *why, *endtoken;
3118                 ChessMove endtype = (ChessMove) 0;
3119
3120                 if (tkind == 0) {
3121                   whitename = star_match[1];
3122                   blackname = star_match[2];
3123                   why = star_match[3];
3124                   endtoken = star_match[4];
3125                 } else {
3126                   whitename = star_match[1];
3127                   blackname = star_match[3];
3128                   why = star_match[5];
3129                   endtoken = star_match[6];
3130                 }
3131
3132                 /* Game start messages */
3133                 if (strncmp(why, "Creating ", 9) == 0 ||
3134                     strncmp(why, "Continuing ", 11) == 0) {
3135                     gs_gamenum = gamenum;
3136                     strcpy(gs_kind, strchr(why, ' ') + 1);
3137 #if ZIPPY
3138                     if (appData.zippyPlay) {
3139                         ZippyGameStart(whitename, blackname);
3140                     }
3141 #endif /*ZIPPY*/
3142                     continue;
3143                 }
3144
3145                 /* Game end messages */
3146                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3147                     ics_gamenum != gamenum) {
3148                     continue;
3149                 }
3150                 while (endtoken[0] == ' ') endtoken++;
3151                 switch (endtoken[0]) {
3152                   case '*':
3153                   default:
3154                     endtype = GameUnfinished;
3155                     break;
3156                   case '0':
3157                     endtype = BlackWins;
3158                     break;
3159                   case '1':
3160                     if (endtoken[1] == '/')
3161                       endtype = GameIsDrawn;
3162                     else
3163                       endtype = WhiteWins;
3164                     break;
3165                 }
3166                 GameEnds(endtype, why, GE_ICS);
3167 #if ZIPPY
3168                 if (appData.zippyPlay && first.initDone) {
3169                     ZippyGameEnd(endtype, why);
3170                     if (first.pr == NULL) {
3171                       /* Start the next process early so that we'll
3172                          be ready for the next challenge */
3173                       StartChessProgram(&first);
3174                     }
3175                     /* Send "new" early, in case this command takes
3176                        a long time to finish, so that we'll be ready
3177                        for the next challenge. */
3178                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3179                     Reset(TRUE, TRUE);
3180                 }
3181 #endif /*ZIPPY*/
3182                 continue;
3183             }
3184
3185             if (looking_at(buf, &i, "Removing game * from observation") ||
3186                 looking_at(buf, &i, "no longer observing game *") ||
3187                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3188                 if (gameMode == IcsObserving &&
3189                     atoi(star_match[0]) == ics_gamenum)
3190                   {
3191                       /* icsEngineAnalyze */
3192                       if (appData.icsEngineAnalyze) {
3193                             ExitAnalyzeMode();
3194                             ModeHighlight();
3195                       }
3196                       StopClocks();
3197                       gameMode = IcsIdle;
3198                       ics_gamenum = -1;
3199                       ics_user_moved = FALSE;
3200                   }
3201                 continue;
3202             }
3203
3204             if (looking_at(buf, &i, "no longer examining game *")) {
3205                 if (gameMode == IcsExamining &&
3206                     atoi(star_match[0]) == ics_gamenum)
3207                   {
3208                       gameMode = IcsIdle;
3209                       ics_gamenum = -1;
3210                       ics_user_moved = FALSE;
3211                   }
3212                 continue;
3213             }
3214
3215             /* Advance leftover_start past any newlines we find,
3216                so only partial lines can get reparsed */
3217             if (looking_at(buf, &i, "\n")) {
3218                 prevColor = curColor;
3219                 if (curColor != ColorNormal) {
3220                     if (oldi > next_out) {
3221                         SendToPlayer(&buf[next_out], oldi - next_out);
3222                         next_out = oldi;
3223                     }
3224                     Colorize(ColorNormal, FALSE);
3225                     curColor = ColorNormal;
3226                 }
3227                 if (started == STARTED_BOARD) {
3228                     started = STARTED_NONE;
3229                     parse[parse_pos] = NULLCHAR;
3230                     ParseBoard12(parse);
3231                     ics_user_moved = 0;
3232
3233                     /* Send premove here */
3234                     if (appData.premove) {
3235                       char str[MSG_SIZ];
3236                       if (currentMove == 0 &&
3237                           gameMode == IcsPlayingWhite &&
3238                           appData.premoveWhite) {
3239                         sprintf(str, "%s\n", appData.premoveWhiteText);
3240                         if (appData.debugMode)
3241                           fprintf(debugFP, "Sending premove:\n");
3242                         SendToICS(str);
3243                       } else if (currentMove == 1 &&
3244                                  gameMode == IcsPlayingBlack &&
3245                                  appData.premoveBlack) {
3246                         sprintf(str, "%s\n", appData.premoveBlackText);
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                         SendToICS(str);
3250                       } else if (gotPremove) {
3251                         gotPremove = 0;
3252                         ClearPremoveHighlights();
3253                         if (appData.debugMode)
3254                           fprintf(debugFP, "Sending premove:\n");
3255                           UserMoveEvent(premoveFromX, premoveFromY, 
3256                                         premoveToX, premoveToY, 
3257                                         premovePromoChar);
3258                       }
3259                     }
3260
3261                     /* Usually suppress following prompt */
3262                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3264                         if (looking_at(buf, &i, "*% ")) {
3265                             savingComment = FALSE;
3266                         }
3267                     }
3268                     next_out = i;
3269                 } else if (started == STARTED_HOLDINGS) {
3270                     int gamenum;
3271                     char new_piece[MSG_SIZ];
3272                     started = STARTED_NONE;
3273                     parse[parse_pos] = NULLCHAR;
3274                     if (appData.debugMode)
3275                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3276                                                         parse, currentMove);
3277                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3278                         gamenum == ics_gamenum) {
3279                         if (gameInfo.variant == VariantNormal) {
3280                           /* [HGM] We seem to switch variant during a game!
3281                            * Presumably no holdings were displayed, so we have
3282                            * to move the position two files to the right to
3283                            * create room for them!
3284                            */
3285                           VariantClass newVariant;
3286                           switch(gameInfo.boardWidth) { // base guess on board width
3287                                 case 9:  newVariant = VariantShogi; break;
3288                                 case 10: newVariant = VariantGreat; break;
3289                                 default: newVariant = VariantCrazyhouse; break;
3290                           }
3291                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3292                           /* Get a move list just to see the header, which
3293                              will tell us whether this is really bug or zh */
3294                           if (ics_getting_history == H_FALSE) {
3295                             ics_getting_history = H_REQUESTED;
3296                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3297                             SendToICS(str);
3298                           }
3299                         }
3300                         new_piece[0] = NULLCHAR;
3301                         sscanf(parse, "game %d white [%s black [%s <- %s",
3302                                &gamenum, white_holding, black_holding,
3303                                new_piece);
3304                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3305                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3306                         /* [HGM] copy holdings to board holdings area */
3307                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3308                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3309                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3310 #if ZIPPY
3311                         if (appData.zippyPlay && first.initDone) {
3312                             ZippyHoldings(white_holding, black_holding,
3313                                           new_piece);
3314                         }
3315 #endif /*ZIPPY*/
3316                         if (tinyLayout || smallLayout) {
3317                             char wh[16], bh[16];
3318                             PackHolding(wh, white_holding);
3319                             PackHolding(bh, black_holding);
3320                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3321                                     gameInfo.white, gameInfo.black);
3322                         } else {
3323                             sprintf(str, "%s [%s] vs. %s [%s]",
3324                                     gameInfo.white, white_holding,
3325                                     gameInfo.black, black_holding);
3326                         }
3327
3328                         DrawPosition(FALSE, boards[currentMove]);
3329                         DisplayTitle(str);
3330                     }
3331                     /* Suppress following prompt */
3332                     if (looking_at(buf, &i, "*% ")) {
3333                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3334                         savingComment = FALSE;
3335                     }
3336                     next_out = i;
3337                 }
3338                 continue;
3339             }
3340
3341             i++;                /* skip unparsed character and loop back */
3342         }
3343         
3344         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3345             started != STARTED_HOLDINGS && i > next_out) {
3346             SendToPlayer(&buf[next_out], i - next_out);
3347             next_out = i;
3348         }
3349         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3350         
3351         leftover_len = buf_len - leftover_start;
3352         /* if buffer ends with something we couldn't parse,
3353            reparse it after appending the next read */
3354         
3355     } else if (count == 0) {
3356         RemoveInputSource(isr);
3357         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3358     } else {
3359         DisplayFatalError(_("Error reading from ICS"), error, 1);
3360     }
3361 }
3362
3363
3364 /* Board style 12 looks like this:
3365    
3366    <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
3367    
3368  * The "<12> " is stripped before it gets to this routine.  The two
3369  * trailing 0's (flip state and clock ticking) are later addition, and
3370  * some chess servers may not have them, or may have only the first.
3371  * Additional trailing fields may be added in the future.  
3372  */
3373
3374 #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"
3375
3376 #define RELATION_OBSERVING_PLAYED    0
3377 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3378 #define RELATION_PLAYING_MYMOVE      1
3379 #define RELATION_PLAYING_NOTMYMOVE  -1
3380 #define RELATION_EXAMINING           2
3381 #define RELATION_ISOLATED_BOARD     -3
3382 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3383
3384 void
3385 ParseBoard12(string)
3386      char *string;
3387
3388     GameMode newGameMode;
3389     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3390     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3391     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3392     char to_play, board_chars[200];
3393     char move_str[500], str[500], elapsed_time[500];
3394     char black[32], white[32];
3395     Board board;
3396     int prevMove = currentMove;
3397     int ticking = 2;
3398     ChessMove moveType;
3399     int fromX, fromY, toX, toY;
3400     char promoChar;
3401     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3402     char *bookHit = NULL; // [HGM] book
3403     Boolean weird = FALSE, reqFlag = FALSE;
3404
3405     fromX = fromY = toX = toY = -1;
3406     
3407     newGame = FALSE;
3408
3409     if (appData.debugMode)
3410       fprintf(debugFP, _("Parsing board: %s\n"), string);
3411
3412     move_str[0] = NULLCHAR;
3413     elapsed_time[0] = NULLCHAR;
3414     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3415         int  i = 0, j;
3416         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3417             if(string[i] == ' ') { ranks++; files = 0; }
3418             else files++;
3419             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3420             i++;
3421         }
3422         for(j = 0; j <i; j++) board_chars[j] = string[j];
3423         board_chars[i] = '\0';
3424         string += i + 1;
3425     }
3426     n = sscanf(string, PATTERN, &to_play, &double_push,
3427                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3428                &gamenum, white, black, &relation, &basetime, &increment,
3429                &white_stren, &black_stren, &white_time, &black_time,
3430                &moveNum, str, elapsed_time, move_str, &ics_flip,
3431                &ticking);
3432
3433     if (n < 21) {
3434         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3435         DisplayError(str, 0);
3436         return;
3437     }
3438
3439     /* Convert the move number to internal form */
3440     moveNum = (moveNum - 1) * 2;
3441     if (to_play == 'B') moveNum++;
3442     if (moveNum >= MAX_MOVES) {
3443       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3444                         0, 1);
3445       return;
3446     }
3447     
3448     switch (relation) {
3449       case RELATION_OBSERVING_PLAYED:
3450       case RELATION_OBSERVING_STATIC:
3451         if (gamenum == -1) {
3452             /* Old ICC buglet */
3453             relation = RELATION_OBSERVING_STATIC;
3454         }
3455         newGameMode = IcsObserving;
3456         break;
3457       case RELATION_PLAYING_MYMOVE:
3458       case RELATION_PLAYING_NOTMYMOVE:
3459         newGameMode =
3460           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3461             IcsPlayingWhite : IcsPlayingBlack;
3462         break;
3463       case RELATION_EXAMINING:
3464         newGameMode = IcsExamining;
3465         break;
3466       case RELATION_ISOLATED_BOARD:
3467       default:
3468         /* Just display this board.  If user was doing something else,
3469            we will forget about it until the next board comes. */ 
3470         newGameMode = IcsIdle;
3471         break;
3472       case RELATION_STARTING_POSITION:
3473         newGameMode = gameMode;
3474         break;
3475     }
3476     
3477     /* Modify behavior for initial board display on move listing
3478        of wild games.
3479        */
3480     switch (ics_getting_history) {
3481       case H_FALSE:
3482       case H_REQUESTED:
3483         break;
3484       case H_GOT_REQ_HEADER:
3485       case H_GOT_UNREQ_HEADER:
3486         /* This is the initial position of the current game */
3487         gamenum = ics_gamenum;
3488         moveNum = 0;            /* old ICS bug workaround */
3489         if (to_play == 'B') {
3490           startedFromSetupPosition = TRUE;
3491           blackPlaysFirst = TRUE;
3492           moveNum = 1;
3493           if (forwardMostMove == 0) forwardMostMove = 1;
3494           if (backwardMostMove == 0) backwardMostMove = 1;
3495           if (currentMove == 0) currentMove = 1;
3496         }
3497         newGameMode = gameMode;
3498         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3499         break;
3500       case H_GOT_UNWANTED_HEADER:
3501         /* This is an initial board that we don't want */
3502         return;
3503       case H_GETTING_MOVES:
3504         /* Should not happen */
3505         DisplayError(_("Error gathering move list: extra board"), 0);
3506         ics_getting_history = H_FALSE;
3507         return;
3508     }
3509
3510    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3511                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3512      /* [HGM] We seem to have switched variant unexpectedly
3513       * Try to guess new variant from board size
3514       */
3515           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3516           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3517           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3518           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3519           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3520           if(!weird) newVariant = VariantNormal;
3521           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3522           /* Get a move list just to see the header, which
3523              will tell us whether this is really bug or zh */
3524           if (ics_getting_history == H_FALSE) {
3525             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3526             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3527             SendToICS(str);
3528           }
3529     }
3530     
3531     /* Take action if this is the first board of a new game, or of a
3532        different game than is currently being displayed.  */
3533     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3534         relation == RELATION_ISOLATED_BOARD) {
3535         
3536         /* Forget the old game and get the history (if any) of the new one */
3537         if (gameMode != BeginningOfGame) {
3538           Reset(TRUE, TRUE);
3539         }
3540         newGame = TRUE;
3541         if (appData.autoRaiseBoard) BoardToTop();
3542         prevMove = -3;
3543         if (gamenum == -1) {
3544             newGameMode = IcsIdle;
3545         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3546                    appData.getMoveList && !reqFlag) {
3547             /* Need to get game history */
3548             ics_getting_history = H_REQUESTED;
3549             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3550             SendToICS(str);
3551         }
3552         
3553         /* Initially flip the board to have black on the bottom if playing
3554            black or if the ICS flip flag is set, but let the user change
3555            it with the Flip View button. */
3556         flipView = appData.autoFlipView ? 
3557           (newGameMode == IcsPlayingBlack) || ics_flip :
3558           appData.flipView;
3559         
3560         /* Done with values from previous mode; copy in new ones */
3561         gameMode = newGameMode;
3562         ModeHighlight();
3563         ics_gamenum = gamenum;
3564         if (gamenum == gs_gamenum) {
3565             int klen = strlen(gs_kind);
3566             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3567             sprintf(str, "ICS %s", gs_kind);
3568             gameInfo.event = StrSave(str);
3569         } else {
3570             gameInfo.event = StrSave("ICS game");
3571         }
3572         gameInfo.site = StrSave(appData.icsHost);
3573         gameInfo.date = PGNDate();
3574         gameInfo.round = StrSave("-");
3575         gameInfo.white = StrSave(white);
3576         gameInfo.black = StrSave(black);
3577         timeControl = basetime * 60 * 1000;
3578         timeControl_2 = 0;
3579         timeIncrement = increment * 1000;
3580         movesPerSession = 0;
3581         gameInfo.timeControl = TimeControlTagValue();
3582         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3583   if (appData.debugMode) {
3584     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3585     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3586     setbuf(debugFP, NULL);
3587   }
3588
3589         gameInfo.outOfBook = NULL;
3590         
3591         /* Do we have the ratings? */
3592         if (strcmp(player1Name, white) == 0 &&
3593             strcmp(player2Name, black) == 0) {
3594             if (appData.debugMode)
3595               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3596                       player1Rating, player2Rating);
3597             gameInfo.whiteRating = player1Rating;
3598             gameInfo.blackRating = player2Rating;
3599         } else if (strcmp(player2Name, white) == 0 &&
3600                    strcmp(player1Name, black) == 0) {
3601             if (appData.debugMode)
3602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3603                       player2Rating, player1Rating);
3604             gameInfo.whiteRating = player2Rating;
3605             gameInfo.blackRating = player1Rating;
3606         }
3607         player1Name[0] = player2Name[0] = NULLCHAR;
3608
3609         /* Silence shouts if requested */
3610         if (appData.quietPlay &&
3611             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3612             SendToICS(ics_prefix);
3613             SendToICS("set shout 0\n");
3614         }
3615     }
3616     
3617     /* Deal with midgame name changes */
3618     if (!newGame) {
3619         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3620             if (gameInfo.white) free(gameInfo.white);
3621             gameInfo.white = StrSave(white);
3622         }
3623         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3624             if (gameInfo.black) free(gameInfo.black);
3625             gameInfo.black = StrSave(black);
3626         }
3627     }
3628     
3629     /* Throw away game result if anything actually changes in examine mode */
3630     if (gameMode == IcsExamining && !newGame) {
3631         gameInfo.result = GameUnfinished;
3632         if (gameInfo.resultDetails != NULL) {
3633             free(gameInfo.resultDetails);
3634             gameInfo.resultDetails = NULL;
3635         }
3636     }
3637     
3638     /* In pausing && IcsExamining mode, we ignore boards coming
3639        in if they are in a different variation than we are. */
3640     if (pauseExamInvalid) return;
3641     if (pausing && gameMode == IcsExamining) {
3642         if (moveNum <= pauseExamForwardMostMove) {
3643             pauseExamInvalid = TRUE;
3644             forwardMostMove = pauseExamForwardMostMove;
3645             return;
3646         }
3647     }
3648     
3649   if (appData.debugMode) {
3650     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3651   }
3652     /* Parse the board */
3653     for (k = 0; k < ranks; k++) {
3654       for (j = 0; j < files; j++)
3655         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3656       if(gameInfo.holdingsWidth > 1) {
3657            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3658            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3659       }
3660     }
3661     CopyBoard(boards[moveNum], board);
3662     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3663     if (moveNum == 0) {
3664         startedFromSetupPosition =
3665           !CompareBoards(board, initialPosition);
3666         if(startedFromSetupPosition)
3667             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3668     }
3669
3670     /* [HGM] Set castling rights. Take the outermost Rooks,
3671        to make it also work for FRC opening positions. Note that board12
3672        is really defective for later FRC positions, as it has no way to
3673        indicate which Rook can castle if they are on the same side of King.
3674        For the initial position we grant rights to the outermost Rooks,
3675        and remember thos rights, and we then copy them on positions
3676        later in an FRC game. This means WB might not recognize castlings with
3677        Rooks that have moved back to their original position as illegal,
3678        but in ICS mode that is not its job anyway.
3679     */
3680     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3681     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3682
3683         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3684             if(board[0][i] == WhiteRook) j = i;
3685         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3686         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3687             if(board[0][i] == WhiteRook) j = i;
3688         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3689         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3690             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3691         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3692         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3693             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3694         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3695
3696         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3698             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3699         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3700             if(board[BOARD_HEIGHT-1][k] == bKing)
3701                 initialRights[5] = castlingRights[moveNum][5] = k;
3702     } else { int r;
3703         r = castlingRights[moveNum][0] = initialRights[0];
3704         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3705         r = castlingRights[moveNum][1] = initialRights[1];
3706         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3707         r = castlingRights[moveNum][3] = initialRights[3];
3708         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3709         r = castlingRights[moveNum][4] = initialRights[4];
3710         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3711         /* wildcastle kludge: always assume King has rights */
3712         r = castlingRights[moveNum][2] = initialRights[2];
3713         r = castlingRights[moveNum][5] = initialRights[5];
3714     }
3715     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3716     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3717
3718     
3719     if (ics_getting_history == H_GOT_REQ_HEADER ||
3720         ics_getting_history == H_GOT_UNREQ_HEADER) {
3721         /* This was an initial position from a move list, not
3722            the current position */
3723         return;
3724     }
3725     
3726     /* Update currentMove and known move number limits */
3727     newMove = newGame || moveNum > forwardMostMove;
3728
3729     if (newGame) {
3730         forwardMostMove = backwardMostMove = currentMove = moveNum;
3731         if (gameMode == IcsExamining && moveNum == 0) {
3732           /* Workaround for ICS limitation: we are not told the wild
3733              type when starting to examine a game.  But if we ask for
3734              the move list, the move list header will tell us */
3735             ics_getting_history = H_REQUESTED;
3736             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3737             SendToICS(str);
3738         }
3739     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3740                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3741 #if ZIPPY
3742         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3743         /* [HGM] applied this also to an engine that is silently watching        */
3744         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3745             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3746             gameInfo.variant == currentlyInitializedVariant) {
3747           takeback = forwardMostMove - moveNum;
3748           for (i = 0; i < takeback; i++) {
3749             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3750             SendToProgram("undo\n", &first);
3751           }
3752         }
3753 #endif
3754
3755         forwardMostMove = moveNum;
3756         if (!pausing || currentMove > forwardMostMove)
3757           currentMove = forwardMostMove;
3758     } else {
3759         /* New part of history that is not contiguous with old part */ 
3760         if (pausing && gameMode == IcsExamining) {
3761             pauseExamInvalid = TRUE;
3762             forwardMostMove = pauseExamForwardMostMove;
3763             return;
3764         }
3765         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3766 #if ZIPPY
3767             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3768                 // [HGM] when we will receive the move list we now request, it will be
3769                 // fed to the engine from the first move on. So if the engine is not
3770                 // in the initial position now, bring it there.
3771                 InitChessProgram(&first, 0);
3772             }
3773 #endif
3774             ics_getting_history = H_REQUESTED;
3775             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3776             SendToICS(str);
3777         }
3778         forwardMostMove = backwardMostMove = currentMove = moveNum;
3779     }
3780     
3781     /* Update the clocks */
3782     if (strchr(elapsed_time, '.')) {
3783       /* Time is in ms */
3784       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3785       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3786     } else {
3787       /* Time is in seconds */
3788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3790     }
3791       
3792
3793 #if ZIPPY
3794     if (appData.zippyPlay && newGame &&
3795         gameMode != IcsObserving && gameMode != IcsIdle &&
3796         gameMode != IcsExamining)
3797       ZippyFirstBoard(moveNum, basetime, increment);
3798 #endif
3799     
3800     /* Put the move on the move list, first converting
3801        to canonical algebraic form. */
3802     if (moveNum > 0) {
3803   if (appData.debugMode) {
3804     if (appData.debugMode) { int f = forwardMostMove;
3805         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3806                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3807     }
3808     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3809     fprintf(debugFP, "moveNum = %d\n", moveNum);
3810     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3811     setbuf(debugFP, NULL);
3812   }
3813         if (moveNum <= backwardMostMove) {
3814             /* We don't know what the board looked like before
3815                this move.  Punt. */
3816             strcpy(parseList[moveNum - 1], move_str);
3817             strcat(parseList[moveNum - 1], " ");
3818             strcat(parseList[moveNum - 1], elapsed_time);
3819             moveList[moveNum - 1][0] = NULLCHAR;
3820         } else if (strcmp(move_str, "none") == 0) {
3821             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3822             /* Again, we don't know what the board looked like;
3823                this is really the start of the game. */
3824             parseList[moveNum - 1][0] = NULLCHAR;
3825             moveList[moveNum - 1][0] = NULLCHAR;
3826             backwardMostMove = moveNum;
3827             startedFromSetupPosition = TRUE;
3828             fromX = fromY = toX = toY = -1;
3829         } else {
3830           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3831           //                 So we parse the long-algebraic move string in stead of the SAN move
3832           int valid; char buf[MSG_SIZ], *prom;
3833
3834           // str looks something like "Q/a1-a2"; kill the slash
3835           if(str[1] == '/') 
3836                 sprintf(buf, "%c%s", str[0], str+2);
3837           else  strcpy(buf, str); // might be castling
3838           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3839                 strcat(buf, prom); // long move lacks promo specification!
3840           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3841                 if(appData.debugMode) 
3842                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3843                 strcpy(move_str, buf);
3844           }
3845           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3846                                 &fromX, &fromY, &toX, &toY, &promoChar)
3847                || ParseOneMove(buf, moveNum - 1, &moveType,
3848                                 &fromX, &fromY, &toX, &toY, &promoChar);
3849           // end of long SAN patch
3850           if (valid) {
3851             (void) CoordsToAlgebraic(boards[moveNum - 1],
3852                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3853                                      fromY, fromX, toY, toX, promoChar,
3854                                      parseList[moveNum-1]);
3855             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3856                              castlingRights[moveNum]) ) {
3857               case MT_NONE:
3858               case MT_STALEMATE:
3859               default:
3860                 break;
3861               case MT_CHECK:
3862                 if(gameInfo.variant != VariantShogi)
3863                     strcat(parseList[moveNum - 1], "+");
3864                 break;
3865               case MT_CHECKMATE:
3866               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3867                 strcat(parseList[moveNum - 1], "#");
3868                 break;
3869             }
3870             strcat(parseList[moveNum - 1], " ");
3871             strcat(parseList[moveNum - 1], elapsed_time);
3872             /* currentMoveString is set as a side-effect of ParseOneMove */
3873             strcpy(moveList[moveNum - 1], currentMoveString);
3874             strcat(moveList[moveNum - 1], "\n");
3875           } else {
3876             /* Move from ICS was illegal!?  Punt. */
3877   if (appData.debugMode) {
3878     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3879     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3880   }
3881             strcpy(parseList[moveNum - 1], move_str);
3882             strcat(parseList[moveNum - 1], " ");
3883             strcat(parseList[moveNum - 1], elapsed_time);
3884             moveList[moveNum - 1][0] = NULLCHAR;
3885             fromX = fromY = toX = toY = -1;
3886           }
3887         }
3888   if (appData.debugMode) {
3889     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3890     setbuf(debugFP, NULL);
3891   }
3892
3893 #if ZIPPY
3894         /* Send move to chess program (BEFORE animating it). */
3895         if (appData.zippyPlay && !newGame && newMove && 
3896            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3897
3898             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3899                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3900                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3901                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3902                             move_str);
3903                     DisplayError(str, 0);
3904                 } else {
3905                     if (first.sendTime) {
3906                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3907                     }
3908                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3909                     if (firstMove && !bookHit) {
3910                         firstMove = FALSE;
3911                         if (first.useColors) {
3912                           SendToProgram(gameMode == IcsPlayingWhite ?
3913                                         "white\ngo\n" :
3914                                         "black\ngo\n", &first);
3915                         } else {
3916                           SendToProgram("go\n", &first);
3917                         }
3918                         first.maybeThinking = TRUE;
3919                     }
3920                 }
3921             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3922               if (moveList[moveNum - 1][0] == NULLCHAR) {
3923                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3924                 DisplayError(str, 0);
3925               } else {
3926                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3927                 SendMoveToProgram(moveNum - 1, &first);
3928               }
3929             }
3930         }
3931 #endif
3932     }
3933
3934     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3935         /* If move comes from a remote source, animate it.  If it
3936            isn't remote, it will have already been animated. */
3937         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3938             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3939         }
3940         if (!pausing && appData.highlightLastMove) {
3941             SetHighlights(fromX, fromY, toX, toY);
3942         }
3943     }
3944     
3945     /* Start the clocks */
3946     whiteFlag = blackFlag = FALSE;
3947     appData.clockMode = !(basetime == 0 && increment == 0);
3948     if (ticking == 0) {
3949       ics_clock_paused = TRUE;
3950       StopClocks();
3951     } else if (ticking == 1) {
3952       ics_clock_paused = FALSE;
3953     }
3954     if (gameMode == IcsIdle ||
3955         relation == RELATION_OBSERVING_STATIC ||
3956         relation == RELATION_EXAMINING ||
3957         ics_clock_paused)
3958       DisplayBothClocks();
3959     else
3960       StartClocks();
3961     
3962     /* Display opponents and material strengths */
3963     if (gameInfo.variant != VariantBughouse &&
3964         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3965         if (tinyLayout || smallLayout) {
3966             if(gameInfo.variant == VariantNormal)
3967                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3968                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3969                     basetime, increment);
3970             else
3971                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3972                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3973                     basetime, increment, (int) gameInfo.variant);
3974         } else {
3975             if(gameInfo.variant == VariantNormal)
3976                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3977                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3978                     basetime, increment);
3979             else
3980                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3981                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3982                     basetime, increment, VariantName(gameInfo.variant));
3983         }
3984         DisplayTitle(str);
3985   if (appData.debugMode) {
3986     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3987   }
3988     }
3989
3990    
3991     /* Display the board */
3992     if (!pausing && !appData.noGUI) {
3993       
3994       if (appData.premove)
3995           if (!gotPremove || 
3996              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3997              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3998               ClearPremoveHighlights();
3999
4000       DrawPosition(FALSE, boards[currentMove]);
4001       DisplayMove(moveNum - 1);
4002       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4003             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4004               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4005         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4006       }
4007     }
4008
4009     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4010 #if ZIPPY
4011     if(bookHit) { // [HGM] book: simulate book reply
4012         static char bookMove[MSG_SIZ]; // a bit generous?
4013
4014         programStats.nodes = programStats.depth = programStats.time = 
4015         programStats.score = programStats.got_only_move = 0;
4016         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4017
4018         strcpy(bookMove, "move ");
4019         strcat(bookMove, bookHit);
4020         HandleMachineMove(bookMove, &first);
4021     }
4022 #endif
4023 }
4024
4025 void
4026 GetMoveListEvent()
4027 {
4028     char buf[MSG_SIZ];
4029     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4030         ics_getting_history = H_REQUESTED;
4031         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4032         SendToICS(buf);
4033     }
4034 }
4035
4036 void
4037 AnalysisPeriodicEvent(force)
4038      int force;
4039 {
4040     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4041          && !force) || !appData.periodicUpdates)
4042       return;
4043
4044     /* Send . command to Crafty to collect stats */
4045     SendToProgram(".\n", &first);
4046
4047     /* Don't send another until we get a response (this makes
4048        us stop sending to old Crafty's which don't understand
4049        the "." command (sending illegal cmds resets node count & time,
4050        which looks bad)) */
4051     programStats.ok_to_send = 0;
4052 }
4053
4054 void ics_update_width(new_width)
4055         int new_width;
4056 {
4057         ics_printf("set width %d\n", new_width);
4058 }
4059
4060 void
4061 SendMoveToProgram(moveNum, cps)
4062      int moveNum;
4063      ChessProgramState *cps;
4064 {
4065     char buf[MSG_SIZ];
4066
4067     if (cps->useUsermove) {
4068       SendToProgram("usermove ", cps);
4069     }
4070     if (cps->useSAN) {
4071       char *space;
4072       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4073         int len = space - parseList[moveNum];
4074         memcpy(buf, parseList[moveNum], len);
4075         buf[len++] = '\n';
4076         buf[len] = NULLCHAR;
4077       } else {
4078         sprintf(buf, "%s\n", parseList[moveNum]);
4079       }
4080       SendToProgram(buf, cps);
4081     } else {
4082       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4083         AlphaRank(moveList[moveNum], 4);
4084         SendToProgram(moveList[moveNum], cps);
4085         AlphaRank(moveList[moveNum], 4); // and back
4086       } else
4087       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4088        * the engine. It would be nice to have a better way to identify castle 
4089        * moves here. */
4090       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4091                                                                          && cps->useOOCastle) {
4092         int fromX = moveList[moveNum][0] - AAA; 
4093         int fromY = moveList[moveNum][1] - ONE;
4094         int toX = moveList[moveNum][2] - AAA; 
4095         int toY = moveList[moveNum][3] - ONE;
4096         if((boards[moveNum][fromY][fromX] == WhiteKing 
4097             && boards[moveNum][toY][toX] == WhiteRook)
4098            || (boards[moveNum][fromY][fromX] == BlackKing 
4099                && boards[moveNum][toY][toX] == BlackRook)) {
4100           if(toX > fromX) SendToProgram("O-O\n", cps);
4101           else SendToProgram("O-O-O\n", cps);
4102         }
4103         else SendToProgram(moveList[moveNum], cps);
4104       }
4105       else SendToProgram(moveList[moveNum], cps);
4106       /* End of additions by Tord */
4107     }
4108
4109     /* [HGM] setting up the opening has brought engine in force mode! */
4110     /*       Send 'go' if we are in a mode where machine should play. */
4111     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4112         (gameMode == TwoMachinesPlay   ||
4113 #ifdef ZIPPY
4114          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4115 #endif
4116          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4117         SendToProgram("go\n", cps);
4118   if (appData.debugMode) {
4119     fprintf(debugFP, "(extra)\n");
4120   }
4121     }
4122     setboardSpoiledMachineBlack = 0;
4123 }
4124
4125 void
4126 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4127      ChessMove moveType;
4128      int fromX, fromY, toX, toY;
4129 {
4130     char user_move[MSG_SIZ];
4131
4132     switch (moveType) {
4133       default:
4134         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4135                 (int)moveType, fromX, fromY, toX, toY);
4136         DisplayError(user_move + strlen("say "), 0);
4137         break;
4138       case WhiteKingSideCastle:
4139       case BlackKingSideCastle:
4140       case WhiteQueenSideCastleWild:
4141       case BlackQueenSideCastleWild:
4142       /* PUSH Fabien */
4143       case WhiteHSideCastleFR:
4144       case BlackHSideCastleFR:
4145       /* POP Fabien */
4146         sprintf(user_move, "o-o\n");
4147         break;
4148       case WhiteQueenSideCastle:
4149       case BlackQueenSideCastle:
4150       case WhiteKingSideCastleWild:
4151       case BlackKingSideCastleWild:
4152       /* PUSH Fabien */
4153       case WhiteASideCastleFR:
4154       case BlackASideCastleFR:
4155       /* POP Fabien */
4156         sprintf(user_move, "o-o-o\n");
4157         break;
4158       case WhitePromotionQueen:
4159       case BlackPromotionQueen:
4160       case WhitePromotionRook:
4161       case BlackPromotionRook:
4162       case WhitePromotionBishop:
4163       case BlackPromotionBishop:
4164       case WhitePromotionKnight:
4165       case BlackPromotionKnight:
4166       case WhitePromotionKing:
4167       case BlackPromotionKing:
4168       case WhitePromotionChancellor:
4169       case BlackPromotionChancellor:
4170       case WhitePromotionArchbishop:
4171       case BlackPromotionArchbishop:
4172         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4173             sprintf(user_move, "%c%c%c%c=%c\n",
4174                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4175                 PieceToChar(WhiteFerz));
4176         else if(gameInfo.variant == VariantGreat)
4177             sprintf(user_move, "%c%c%c%c=%c\n",
4178                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4179                 PieceToChar(WhiteMan));
4180         else
4181             sprintf(user_move, "%c%c%c%c=%c\n",
4182                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4183                 PieceToChar(PromoPiece(moveType)));
4184         break;
4185       case WhiteDrop:
4186       case BlackDrop:
4187         sprintf(user_move, "%c@%c%c\n",
4188                 ToUpper(PieceToChar((ChessSquare) fromX)),
4189                 AAA + toX, ONE + toY);
4190         break;
4191       case NormalMove:
4192       case WhiteCapturesEnPassant:
4193       case BlackCapturesEnPassant:
4194       case IllegalMove:  /* could be a variant we don't quite understand */
4195         sprintf(user_move, "%c%c%c%c\n",
4196                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4197         break;
4198     }
4199     SendToICS(user_move);
4200     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4201         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4202 }
4203
4204 void
4205 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4206      int rf, ff, rt, ft;
4207      char promoChar;
4208      char move[7];
4209 {
4210     if (rf == DROP_RANK) {
4211         sprintf(move, "%c@%c%c\n",
4212                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4213     } else {
4214         if (promoChar == 'x' || promoChar == NULLCHAR) {
4215             sprintf(move, "%c%c%c%c\n",
4216                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4217         } else {
4218             sprintf(move, "%c%c%c%c%c\n",
4219                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4220         }
4221     }
4222 }
4223
4224 void
4225 ProcessICSInitScript(f)
4226      FILE *f;
4227 {
4228     char buf[MSG_SIZ];
4229
4230     while (fgets(buf, MSG_SIZ, f)) {
4231         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4232     }
4233
4234     fclose(f);
4235 }
4236
4237
4238 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4239 void
4240 AlphaRank(char *move, int n)
4241 {
4242 //    char *p = move, c; int x, y;
4243
4244     if (appData.debugMode) {
4245         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4246     }
4247
4248     if(move[1]=='*' && 
4249        move[2]>='0' && move[2]<='9' &&
4250        move[3]>='a' && move[3]<='x'    ) {
4251         move[1] = '@';
4252         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4253         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4254     } else
4255     if(move[0]>='0' && move[0]<='9' &&
4256        move[1]>='a' && move[1]<='x' &&
4257        move[2]>='0' && move[2]<='9' &&
4258        move[3]>='a' && move[3]<='x'    ) {
4259         /* input move, Shogi -> normal */
4260         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4261         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4262         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4263         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4264     } else
4265     if(move[1]=='@' &&
4266        move[3]>='0' && move[3]<='9' &&
4267        move[2]>='a' && move[2]<='x'    ) {
4268         move[1] = '*';
4269         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4270         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4271     } else
4272     if(
4273        move[0]>='a' && move[0]<='x' &&
4274        move[3]>='0' && move[3]<='9' &&
4275        move[2]>='a' && move[2]<='x'    ) {
4276          /* output move, normal -> Shogi */
4277         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4278         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4279         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4281         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4282     }
4283     if (appData.debugMode) {
4284         fprintf(debugFP, "   out = '%s'\n", move);
4285     }
4286 }
4287
4288 /* Parser for moves from gnuchess, ICS, or user typein box */
4289 Boolean
4290 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4291      char *move;
4292      int moveNum;
4293      ChessMove *moveType;
4294      int *fromX, *fromY, *toX, *toY;
4295      char *promoChar;
4296 {       
4297     if (appData.debugMode) {
4298         fprintf(debugFP, "move to parse: %s\n", move);
4299     }
4300     *moveType = yylexstr(moveNum, move);
4301
4302     switch (*moveType) {
4303       case WhitePromotionChancellor:
4304       case BlackPromotionChancellor:
4305       case WhitePromotionArchbishop:
4306       case BlackPromotionArchbishop:
4307       case WhitePromotionQueen:
4308       case BlackPromotionQueen:
4309       case WhitePromotionRook:
4310       case BlackPromotionRook:
4311       case WhitePromotionBishop:
4312       case BlackPromotionBishop:
4313       case WhitePromotionKnight:
4314       case BlackPromotionKnight:
4315       case WhitePromotionKing:
4316       case BlackPromotionKing:
4317       case NormalMove:
4318       case WhiteCapturesEnPassant:
4319       case BlackCapturesEnPassant:
4320       case WhiteKingSideCastle:
4321       case WhiteQueenSideCastle:
4322       case BlackKingSideCastle:
4323       case BlackQueenSideCastle:
4324       case WhiteKingSideCastleWild:
4325       case WhiteQueenSideCastleWild:
4326       case BlackKingSideCastleWild:
4327       case BlackQueenSideCastleWild:
4328       /* Code added by Tord: */
4329       case WhiteHSideCastleFR:
4330       case WhiteASideCastleFR:
4331       case BlackHSideCastleFR:
4332       case BlackASideCastleFR:
4333       /* End of code added by Tord */
4334       case IllegalMove:         /* bug or odd chess variant */
4335         *fromX = currentMoveString[0] - AAA;
4336         *fromY = currentMoveString[1] - ONE;
4337         *toX = currentMoveString[2] - AAA;
4338         *toY = currentMoveString[3] - ONE;
4339         *promoChar = currentMoveString[4];
4340         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4341             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4342     if (appData.debugMode) {
4343         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4344     }
4345             *fromX = *fromY = *toX = *toY = 0;
4346             return FALSE;
4347         }
4348         if (appData.testLegality) {
4349           return (*moveType != IllegalMove);
4350         } else {
4351           return !(fromX == fromY && toX == toY);
4352         }
4353
4354       case WhiteDrop:
4355       case BlackDrop:
4356         *fromX = *moveType == WhiteDrop ?
4357           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4358           (int) CharToPiece(ToLower(currentMoveString[0]));
4359         *fromY = DROP_RANK;
4360         *toX = currentMoveString[2] - AAA;
4361         *toY = currentMoveString[3] - ONE;
4362         *promoChar = NULLCHAR;
4363         return TRUE;
4364
4365       case AmbiguousMove:
4366       case ImpossibleMove:
4367       case (ChessMove) 0:       /* end of file */
4368       case ElapsedTime:
4369       case Comment:
4370       case PGNTag:
4371       case NAG:
4372       case WhiteWins:
4373       case BlackWins:
4374       case GameIsDrawn:
4375       default:
4376     if (appData.debugMode) {
4377         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4378     }
4379         /* bug? */
4380         *fromX = *fromY = *toX = *toY = 0;
4381         *promoChar = NULLCHAR;
4382         return FALSE;
4383     }
4384 }
4385
4386 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4387 // All positions will have equal probability, but the current method will not provide a unique
4388 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4389 #define DARK 1
4390 #define LITE 2
4391 #define ANY 3
4392
4393 int squaresLeft[4];
4394 int piecesLeft[(int)BlackPawn];
4395 int seed, nrOfShuffles;
4396
4397 void GetPositionNumber()
4398 {       // sets global variable seed
4399         int i;
4400
4401         seed = appData.defaultFrcPosition;
4402         if(seed < 0) { // randomize based on time for negative FRC position numbers
4403                 for(i=0; i<50; i++) seed += random();
4404                 seed = random() ^ random() >> 8 ^ random() << 8;
4405                 if(seed<0) seed = -seed;
4406         }
4407 }
4408
4409 int put(Board board, int pieceType, int rank, int n, int shade)
4410 // put the piece on the (n-1)-th empty squares of the given shade
4411 {
4412         int i;
4413
4414         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4415                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4416                         board[rank][i] = (ChessSquare) pieceType;
4417                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4418                         squaresLeft[ANY]--;
4419                         piecesLeft[pieceType]--; 
4420                         return i;
4421                 }
4422         }
4423         return -1;
4424 }
4425
4426
4427 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4428 // calculate where the next piece goes, (any empty square), and put it there
4429 {
4430         int i;
4431
4432         i = seed % squaresLeft[shade];
4433         nrOfShuffles *= squaresLeft[shade];
4434         seed /= squaresLeft[shade];
4435         put(board, pieceType, rank, i, shade);
4436 }
4437
4438 void AddTwoPieces(Board board, int pieceType, int rank)
4439 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4440 {
4441         int i, n=squaresLeft[ANY], j=n-1, k;
4442
4443         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4444         i = seed % k;  // pick one
4445         nrOfShuffles *= k;
4446         seed /= k;
4447         while(i >= j) i -= j--;
4448         j = n - 1 - j; i += j;
4449         put(board, pieceType, rank, j, ANY);
4450         put(board, pieceType, rank, i, ANY);
4451 }
4452
4453 void SetUpShuffle(Board board, int number)
4454 {
4455         int i, p, first=1;
4456
4457         GetPositionNumber(); nrOfShuffles = 1;
4458
4459         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4460         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4461         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4462
4463         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4464
4465         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4466             p = (int) board[0][i];
4467             if(p < (int) BlackPawn) piecesLeft[p] ++;
4468             board[0][i] = EmptySquare;
4469         }
4470
4471         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4472             // shuffles restricted to allow normal castling put KRR first
4473             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4474                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4475             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4476                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4477             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4478                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4479             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4480                 put(board, WhiteRook, 0, 0, ANY);
4481             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4482         }
4483
4484         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4485             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4486             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4487                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4488                 while(piecesLeft[p] >= 2) {
4489                     AddOnePiece(board, p, 0, LITE);
4490                     AddOnePiece(board, p, 0, DARK);
4491                 }
4492                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4493             }
4494
4495         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4496             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4497             // but we leave King and Rooks for last, to possibly obey FRC restriction
4498             if(p == (int)WhiteRook) continue;
4499             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4500             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4501         }
4502
4503         // now everything is placed, except perhaps King (Unicorn) and Rooks
4504
4505         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4506             // Last King gets castling rights
4507             while(piecesLeft[(int)WhiteUnicorn]) {
4508                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4509                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4510             }
4511
4512             while(piecesLeft[(int)WhiteKing]) {
4513                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4514                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4515             }
4516
4517
4518         } else {
4519             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4520             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4521         }
4522
4523         // Only Rooks can be left; simply place them all
4524         while(piecesLeft[(int)WhiteRook]) {
4525                 i = put(board, WhiteRook, 0, 0, ANY);
4526                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4527                         if(first) {
4528                                 first=0;
4529                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4530                         }
4531                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4532                 }
4533         }
4534         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4535             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4536         }
4537
4538         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4539 }
4540
4541 int SetCharTable( char *table, const char * map )
4542 /* [HGM] moved here from winboard.c because of its general usefulness */
4543 /*       Basically a safe strcpy that uses the last character as King */
4544 {
4545     int result = FALSE; int NrPieces;
4546
4547     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4548                     && NrPieces >= 12 && !(NrPieces&1)) {
4549         int i; /* [HGM] Accept even length from 12 to 34 */
4550
4551         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4552         for( i=0; i<NrPieces/2-1; i++ ) {
4553             table[i] = map[i];
4554             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4555         }
4556         table[(int) WhiteKing]  = map[NrPieces/2-1];
4557         table[(int) BlackKing]  = map[NrPieces-1];
4558
4559         result = TRUE;
4560     }
4561
4562     return result;
4563 }
4564
4565 void Prelude(Board board)
4566 {       // [HGM] superchess: random selection of exo-pieces
4567         int i, j, k; ChessSquare p; 
4568         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4569
4570         GetPositionNumber(); // use FRC position number
4571
4572         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4573             SetCharTable(pieceToChar, appData.pieceToCharTable);
4574             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4575                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4576         }
4577
4578         j = seed%4;                 seed /= 4; 
4579         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4580         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4581         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4582         j = seed%3 + (seed%3 >= j); seed /= 3; 
4583         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4584         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4585         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4586         j = seed%3;                 seed /= 3; 
4587         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4588         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4589         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4590         j = seed%2 + (seed%2 >= j); seed /= 2; 
4591         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4592         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4593         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4594         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4595         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4596         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4597         put(board, exoPieces[0],    0, 0, ANY);
4598         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4599 }
4600
4601 void
4602 InitPosition(redraw)
4603      int redraw;
4604 {
4605     ChessSquare (* pieces)[BOARD_SIZE];
4606     int i, j, pawnRow, overrule,
4607     oldx = gameInfo.boardWidth,
4608     oldy = gameInfo.boardHeight,
4609     oldh = gameInfo.holdingsWidth,
4610     oldv = gameInfo.variant;
4611
4612     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4613
4614     /* [AS] Initialize pv info list [HGM] and game status */
4615     {
4616         for( i=0; i<MAX_MOVES; i++ ) {
4617             pvInfoList[i].depth = 0;
4618             epStatus[i]=EP_NONE;
4619             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4620         }
4621
4622         initialRulePlies = 0; /* 50-move counter start */
4623
4624         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4625         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4626     }
4627
4628     
4629     /* [HGM] logic here is completely changed. In stead of full positions */
4630     /* the initialized data only consist of the two backranks. The switch */
4631     /* selects which one we will use, which is than copied to the Board   */
4632     /* initialPosition, which for the rest is initialized by Pawns and    */
4633     /* empty squares. This initial position is then copied to boards[0],  */
4634     /* possibly after shuffling, so that it remains available.            */
4635
4636     gameInfo.holdingsWidth = 0; /* default board sizes */
4637     gameInfo.boardWidth    = 8;
4638     gameInfo.boardHeight   = 8;
4639     gameInfo.holdingsSize  = 0;
4640     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4641     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4642     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4643
4644     switch (gameInfo.variant) {
4645     case VariantFischeRandom:
4646       shuffleOpenings = TRUE;
4647     default:
4648       pieces = FIDEArray;
4649       break;
4650     case VariantShatranj:
4651       pieces = ShatranjArray;
4652       nrCastlingRights = 0;
4653       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4654       break;
4655     case VariantTwoKings:
4656       pieces = twoKingsArray;
4657       break;
4658     case VariantCapaRandom:
4659       shuffleOpenings = TRUE;
4660     case VariantCapablanca:
4661       pieces = CapablancaArray;
4662       gameInfo.boardWidth = 10;
4663       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4664       break;
4665     case VariantGothic:
4666       pieces = GothicArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4669       break;
4670     case VariantJanus:
4671       pieces = JanusArray;
4672       gameInfo.boardWidth = 10;
4673       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4674       nrCastlingRights = 6;
4675         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4676         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4677         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4678         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4679         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4680         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4681       break;
4682     case VariantFalcon:
4683       pieces = FalconArray;
4684       gameInfo.boardWidth = 10;
4685       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4686       break;
4687     case VariantXiangqi:
4688       pieces = XiangqiArray;
4689       gameInfo.boardWidth  = 9;
4690       gameInfo.boardHeight = 10;
4691       nrCastlingRights = 0;
4692       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4693       break;
4694     case VariantShogi:
4695       pieces = ShogiArray;
4696       gameInfo.boardWidth  = 9;
4697       gameInfo.boardHeight = 9;
4698       gameInfo.holdingsSize = 7;
4699       nrCastlingRights = 0;
4700       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4701       break;
4702     case VariantCourier:
4703       pieces = CourierArray;
4704       gameInfo.boardWidth  = 12;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4707       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4708       break;
4709     case VariantKnightmate:
4710       pieces = KnightmateArray;
4711       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4712       break;
4713     case VariantFairy:
4714       pieces = fairyArray;
4715       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4716       break;
4717     case VariantGreat:
4718       pieces = GreatArray;
4719       gameInfo.boardWidth = 10;
4720       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4721       gameInfo.holdingsSize = 8;
4722       break;
4723     case VariantSuper:
4724       pieces = FIDEArray;
4725       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4726       gameInfo.holdingsSize = 8;
4727       startedFromSetupPosition = TRUE;
4728       break;
4729     case VariantCrazyhouse:
4730     case VariantBughouse:
4731       pieces = FIDEArray;
4732       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4733       gameInfo.holdingsSize = 5;
4734       break;
4735     case VariantWildCastle:
4736       pieces = FIDEArray;
4737       /* !!?shuffle with kings guaranteed to be on d or e file */
4738       shuffleOpenings = 1;
4739       break;
4740     case VariantNoCastle:
4741       pieces = FIDEArray;
4742       nrCastlingRights = 0;
4743       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4744       /* !!?unconstrained back-rank shuffle */
4745       shuffleOpenings = 1;
4746       break;
4747     }
4748
4749     overrule = 0;
4750     if(appData.NrFiles >= 0) {
4751         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4752         gameInfo.boardWidth = appData.NrFiles;
4753     }
4754     if(appData.NrRanks >= 0) {
4755         gameInfo.boardHeight = appData.NrRanks;
4756     }
4757     if(appData.holdingsSize >= 0) {
4758         i = appData.holdingsSize;
4759         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4760         gameInfo.holdingsSize = i;
4761     }
4762     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4763     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4764         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4765
4766     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4767     if(pawnRow < 1) pawnRow = 1;
4768
4769     /* User pieceToChar list overrules defaults */
4770     if(appData.pieceToCharTable != NULL)
4771         SetCharTable(pieceToChar, appData.pieceToCharTable);
4772
4773     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4774
4775         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4776             s = (ChessSquare) 0; /* account holding counts in guard band */
4777         for( i=0; i<BOARD_HEIGHT; i++ )
4778             initialPosition[i][j] = s;
4779
4780         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4781         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4782         initialPosition[pawnRow][j] = WhitePawn;
4783         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4784         if(gameInfo.variant == VariantXiangqi) {
4785             if(j&1) {
4786                 initialPosition[pawnRow][j] = 
4787                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4788                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4789                    initialPosition[2][j] = WhiteCannon;
4790                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4791                 }
4792             }
4793         }
4794         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4795     }
4796     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4797
4798             j=BOARD_LEFT+1;
4799             initialPosition[1][j] = WhiteBishop;
4800             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4801             j=BOARD_RGHT-2;
4802             initialPosition[1][j] = WhiteRook;
4803             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4804     }
4805
4806     if( nrCastlingRights == -1) {
4807         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4808         /*       This sets default castling rights from none to normal corners   */
4809         /* Variants with other castling rights must set them themselves above    */
4810         nrCastlingRights = 6;
4811        
4812         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4813         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4814         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4815         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4816         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4817         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4818      }
4819
4820      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4821      if(gameInfo.variant == VariantGreat) { // promotion commoners
4822         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4823         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4824         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4825         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4826      }
4827   if (appData.debugMode) {
4828     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4829   }
4830     if(shuffleOpenings) {
4831         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4832         startedFromSetupPosition = TRUE;
4833     }
4834     if(startedFromPositionFile) {
4835       /* [HGM] loadPos: use PositionFile for every new game */
4836       CopyBoard(initialPosition, filePosition);
4837       for(i=0; i<nrCastlingRights; i++)
4838           castlingRights[0][i] = initialRights[i] = fileRights[i];
4839       startedFromSetupPosition = TRUE;
4840     }
4841
4842     CopyBoard(boards[0], initialPosition);
4843
4844     if(oldx != gameInfo.boardWidth ||
4845        oldy != gameInfo.boardHeight ||
4846        oldh != gameInfo.holdingsWidth
4847 #ifdef GOTHIC
4848        || oldv == VariantGothic ||        // For licensing popups
4849        gameInfo.variant == VariantGothic
4850 #endif
4851 #ifdef FALCON
4852        || oldv == VariantFalcon ||
4853        gameInfo.variant == VariantFalcon
4854 #endif
4855                                          )
4856             InitDrawingSizes(-2 ,0);
4857
4858     if (redraw)
4859       DrawPosition(TRUE, boards[currentMove]);
4860 }
4861
4862 void
4863 SendBoard(cps, moveNum)
4864      ChessProgramState *cps;
4865      int moveNum;
4866 {
4867     char message[MSG_SIZ];
4868     
4869     if (cps->useSetboard) {
4870       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4871       sprintf(message, "setboard %s\n", fen);
4872       SendToProgram(message, cps);
4873       free(fen);
4874
4875     } else {
4876       ChessSquare *bp;
4877       int i, j;
4878       /* Kludge to set black to move, avoiding the troublesome and now
4879        * deprecated "black" command.
4880        */
4881       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4882
4883       SendToProgram("edit\n", cps);
4884       SendToProgram("#\n", cps);
4885       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4886         bp = &boards[moveNum][i][BOARD_LEFT];
4887         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4888           if ((int) *bp < (int) BlackPawn) {
4889             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4890                     AAA + j, ONE + i);
4891             if(message[0] == '+' || message[0] == '~') {
4892                 sprintf(message, "%c%c%c+\n",
4893                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4894                         AAA + j, ONE + i);
4895             }
4896             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4897                 message[1] = BOARD_RGHT   - 1 - j + '1';
4898                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4899             }
4900             SendToProgram(message, cps);
4901           }
4902         }
4903       }
4904     
4905       SendToProgram("c\n", cps);
4906       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4907         bp = &boards[moveNum][i][BOARD_LEFT];
4908         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4909           if (((int) *bp != (int) EmptySquare)
4910               && ((int) *bp >= (int) BlackPawn)) {
4911             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4912                     AAA + j, ONE + i);
4913             if(message[0] == '+' || message[0] == '~') {
4914                 sprintf(message, "%c%c%c+\n",
4915                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4916                         AAA + j, ONE + i);
4917             }
4918             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4919                 message[1] = BOARD_RGHT   - 1 - j + '1';
4920                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4921             }
4922             SendToProgram(message, cps);
4923           }
4924         }
4925       }
4926     
4927       SendToProgram(".\n", cps);
4928     }
4929     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4930 }
4931
4932 int
4933 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4934 {
4935     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4936     /* [HGM] add Shogi promotions */
4937     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4938     ChessSquare piece;
4939     ChessMove moveType;
4940     Boolean premove;
4941
4942     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4943     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4944
4945     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4946       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4947         return FALSE;
4948
4949     piece = boards[currentMove][fromY][fromX];
4950     if(gameInfo.variant == VariantShogi) {
4951         promotionZoneSize = 3;
4952         highestPromotingPiece = (int)WhiteFerz;
4953     }
4954
4955     // next weed out all moves that do not touch the promotion zone at all
4956     if((int)piece >= BlackPawn) {
4957         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4958              return FALSE;
4959         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4960     } else {
4961         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4962            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4963     }
4964
4965     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4966
4967     // weed out mandatory Shogi promotions
4968     if(gameInfo.variant == VariantShogi) {
4969         if(piece >= BlackPawn) {
4970             if(toY == 0 && piece == BlackPawn ||
4971                toY == 0 && piece == BlackQueen ||
4972                toY <= 1 && piece == BlackKnight) {
4973                 *promoChoice = '+';
4974                 return FALSE;
4975             }
4976         } else {
4977             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4978                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4979                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4980                 *promoChoice = '+';
4981                 return FALSE;
4982             }
4983         }
4984     }
4985
4986     // weed out obviously illegal Pawn moves
4987     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4988         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4989         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4990         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4991         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4992         // note we are not allowed to test for valid (non-)capture, due to premove
4993     }
4994
4995     // we either have a choice what to promote to, or (in Shogi) whether to promote
4996     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4997         *promoChoice = PieceToChar(BlackFerz);  // no choice
4998         return FALSE;
4999     }
5000     if(appData.alwaysPromoteToQueen) { // predetermined
5001         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5002              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5003         else *promoChoice = PieceToChar(BlackQueen);
5004         return FALSE;
5005     }
5006
5007     // suppress promotion popup on illegal moves that are not premoves
5008     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5009               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5010     if(appData.testLegality && !premove) {
5011         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5012                         epStatus[currentMove], castlingRights[currentMove],
5013                         fromY, fromX, toY, toX, NULLCHAR);
5014         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5015            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5016             return FALSE;
5017     }
5018
5019     return TRUE;
5020 }
5021
5022 int
5023 InPalace(row, column)
5024      int row, column;
5025 {   /* [HGM] for Xiangqi */
5026     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5027          column < (BOARD_WIDTH + 4)/2 &&
5028          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5029     return FALSE;
5030 }
5031
5032 int
5033 PieceForSquare (x, y)
5034      int x;
5035      int y;
5036 {
5037   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5038      return -1;
5039   else
5040      return boards[currentMove][y][x];
5041 }
5042
5043 int
5044 OKToStartUserMove(x, y)
5045      int x, y;
5046 {
5047     ChessSquare from_piece;
5048     int white_piece;
5049
5050     if (matchMode) return FALSE;
5051     if (gameMode == EditPosition) return TRUE;
5052
5053     if (x >= 0 && y >= 0)
5054       from_piece = boards[currentMove][y][x];
5055     else
5056       from_piece = EmptySquare;
5057
5058     if (from_piece == EmptySquare) return FALSE;
5059
5060     white_piece = (int)from_piece >= (int)WhitePawn &&
5061       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5062
5063     switch (gameMode) {
5064       case PlayFromGameFile:
5065       case AnalyzeFile:
5066       case TwoMachinesPlay:
5067       case EndOfGame:
5068         return FALSE;
5069
5070       case IcsObserving:
5071       case IcsIdle:
5072         return FALSE;
5073
5074       case MachinePlaysWhite:
5075       case IcsPlayingBlack:
5076         if (appData.zippyPlay) return FALSE;
5077         if (white_piece) {
5078             DisplayMoveError(_("You are playing Black"));
5079             return FALSE;
5080         }
5081         break;
5082
5083       case MachinePlaysBlack:
5084       case IcsPlayingWhite:
5085         if (appData.zippyPlay) return FALSE;
5086         if (!white_piece) {
5087             DisplayMoveError(_("You are playing White"));
5088             return FALSE;
5089         }
5090         break;
5091
5092       case EditGame:
5093         if (!white_piece && WhiteOnMove(currentMove)) {
5094             DisplayMoveError(_("It is White's turn"));
5095             return FALSE;
5096         }           
5097         if (white_piece && !WhiteOnMove(currentMove)) {
5098             DisplayMoveError(_("It is Black's turn"));
5099             return FALSE;
5100         }           
5101         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5102             /* Editing correspondence game history */
5103             /* Could disallow this or prompt for confirmation */
5104             cmailOldMove = -1;
5105         }
5106         if (currentMove < forwardMostMove) {
5107             /* Discarding moves */
5108             /* Could prompt for confirmation here,
5109                but I don't think that's such a good idea */
5110             forwardMostMove = currentMove;
5111         }
5112         break;
5113
5114       case BeginningOfGame:
5115         if (appData.icsActive) return FALSE;
5116         if (!appData.noChessProgram) {
5117             if (!white_piece) {
5118                 DisplayMoveError(_("You are playing White"));
5119                 return FALSE;
5120             }
5121         }
5122         break;
5123         
5124       case Training:
5125         if (!white_piece && WhiteOnMove(currentMove)) {
5126             DisplayMoveError(_("It is White's turn"));
5127             return FALSE;
5128         }           
5129         if (white_piece && !WhiteOnMove(currentMove)) {
5130             DisplayMoveError(_("It is Black's turn"));
5131             return FALSE;
5132         }           
5133         break;
5134
5135       default:
5136       case IcsExamining:
5137         break;
5138     }
5139     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5140         && gameMode != AnalyzeFile && gameMode != Training) {
5141         DisplayMoveError(_("Displayed position is not current"));
5142         return FALSE;
5143     }
5144     return TRUE;
5145 }
5146
5147 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5148 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5149 int lastLoadGameUseList = FALSE;
5150 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5151 ChessMove lastLoadGameStart = (ChessMove) 0;
5152
5153 ChessMove
5154 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5155      int fromX, fromY, toX, toY;
5156      int promoChar;
5157      Boolean captureOwn;
5158 {
5159     ChessMove moveType;
5160     ChessSquare pdown, pup;
5161
5162     /* Check if the user is playing in turn.  This is complicated because we
5163        let the user "pick up" a piece before it is his turn.  So the piece he
5164        tried to pick up may have been captured by the time he puts it down!
5165        Therefore we use the color the user is supposed to be playing in this
5166        test, not the color of the piece that is currently on the starting
5167        square---except in EditGame mode, where the user is playing both
5168        sides; fortunately there the capture race can't happen.  (It can
5169        now happen in IcsExamining mode, but that's just too bad.  The user
5170        will get a somewhat confusing message in that case.)
5171        */
5172
5173     switch (gameMode) {
5174       case PlayFromGameFile:
5175       case AnalyzeFile:
5176       case TwoMachinesPlay:
5177       case EndOfGame:
5178       case IcsObserving:
5179       case IcsIdle:
5180         /* We switched into a game mode where moves are not accepted,
5181            perhaps while the mouse button was down. */
5182         return ImpossibleMove;
5183
5184       case MachinePlaysWhite:
5185         /* User is moving for Black */
5186         if (WhiteOnMove(currentMove)) {
5187             DisplayMoveError(_("It is White's turn"));
5188             return ImpossibleMove;
5189         }
5190         break;
5191
5192       case MachinePlaysBlack:
5193         /* User is moving for White */
5194         if (!WhiteOnMove(currentMove)) {
5195             DisplayMoveError(_("It is Black's turn"));
5196             return ImpossibleMove;
5197         }
5198         break;
5199
5200       case EditGame:
5201       case IcsExamining:
5202       case BeginningOfGame:
5203       case AnalyzeMode:
5204       case Training:
5205         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5206             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5207             /* User is moving for Black */
5208             if (WhiteOnMove(currentMove)) {
5209                 DisplayMoveError(_("It is White's turn"));
5210                 return ImpossibleMove;
5211             }
5212         } else {
5213             /* User is moving for White */
5214             if (!WhiteOnMove(currentMove)) {
5215                 DisplayMoveError(_("It is Black's turn"));
5216                 return ImpossibleMove;
5217             }
5218         }
5219         break;
5220
5221       case IcsPlayingBlack:
5222         /* User is moving for Black */
5223         if (WhiteOnMove(currentMove)) {
5224             if (!appData.premove) {
5225                 DisplayMoveError(_("It is White's turn"));
5226             } else if (toX >= 0 && toY >= 0) {
5227                 premoveToX = toX;
5228                 premoveToY = toY;
5229                 premoveFromX = fromX;
5230                 premoveFromY = fromY;
5231                 premovePromoChar = promoChar;
5232                 gotPremove = 1;
5233                 if (appData.debugMode) 
5234                     fprintf(debugFP, "Got premove: fromX %d,"
5235                             "fromY %d, toX %d, toY %d\n",
5236                             fromX, fromY, toX, toY);
5237             }
5238             return ImpossibleMove;
5239         }
5240         break;
5241
5242       case IcsPlayingWhite:
5243         /* User is moving for White */
5244         if (!WhiteOnMove(currentMove)) {
5245             if (!appData.premove) {
5246                 DisplayMoveError(_("It is Black's turn"));
5247             } else if (toX >= 0 && toY >= 0) {
5248                 premoveToX = toX;
5249                 premoveToY = toY;
5250                 premoveFromX = fromX;
5251                 premoveFromY = fromY;
5252                 premovePromoChar = promoChar;
5253                 gotPremove = 1;
5254                 if (appData.debugMode) 
5255                     fprintf(debugFP, "Got premove: fromX %d,"
5256                             "fromY %d, toX %d, toY %d\n",
5257                             fromX, fromY, toX, toY);
5258             }
5259             return ImpossibleMove;
5260         }
5261         break;
5262
5263       default:
5264         break;
5265
5266       case EditPosition:
5267         /* EditPosition, empty square, or different color piece;
5268            click-click move is possible */
5269         if (toX == -2 || toY == -2) {
5270             boards[0][fromY][fromX] = EmptySquare;
5271             return AmbiguousMove;
5272         } else if (toX >= 0 && toY >= 0) {
5273             boards[0][toY][toX] = boards[0][fromY][fromX];
5274             boards[0][fromY][fromX] = EmptySquare;
5275             return AmbiguousMove;
5276         }
5277         return ImpossibleMove;
5278     }
5279
5280     if(toX < 0 || toY < 0) return ImpossibleMove;
5281     pdown = boards[currentMove][fromY][fromX];
5282     pup = boards[currentMove][toY][toX];
5283
5284     /* [HGM] If move started in holdings, it means a drop */
5285     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5286          if( pup != EmptySquare ) return ImpossibleMove;
5287          if(appData.testLegality) {
5288              /* it would be more logical if LegalityTest() also figured out
5289               * which drops are legal. For now we forbid pawns on back rank.
5290               * Shogi is on its own here...
5291               */
5292              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5293                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5294                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5295          }
5296          return WhiteDrop; /* Not needed to specify white or black yet */
5297     }
5298
5299     userOfferedDraw = FALSE;
5300         
5301     /* [HGM] always test for legality, to get promotion info */
5302     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5303                           epStatus[currentMove], castlingRights[currentMove],
5304                                          fromY, fromX, toY, toX, promoChar);
5305     /* [HGM] but possibly ignore an IllegalMove result */
5306     if (appData.testLegality) {
5307         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5308             DisplayMoveError(_("Illegal move"));
5309             return ImpossibleMove;
5310         }
5311     }
5312
5313     return moveType;
5314     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5315        function is made into one that returns an OK move type if FinishMove
5316        should be called. This to give the calling driver routine the
5317        opportunity to finish the userMove input with a promotion popup,
5318        without bothering the user with this for invalid or illegal moves */
5319
5320 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5321 }
5322
5323 /* Common tail of UserMoveEvent and DropMenuEvent */
5324 int
5325 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5326      ChessMove moveType;
5327      int fromX, fromY, toX, toY;
5328      /*char*/int promoChar;
5329 {
5330     char *bookHit = 0;
5331
5332     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5333         // [HGM] superchess: suppress promotions to non-available piece
5334         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5335         if(WhiteOnMove(currentMove)) {
5336             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5337         } else {
5338             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5339         }
5340     }
5341
5342     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5343        move type in caller when we know the move is a legal promotion */
5344     if(moveType == NormalMove && promoChar)
5345         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5346
5347     /* [HGM] convert drag-and-drop piece drops to standard form */
5348     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5349          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5350            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5351                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5352            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5353            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5354            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5355            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5356          fromY = DROP_RANK;
5357     }
5358
5359     /* [HGM] <popupFix> The following if has been moved here from
5360        UserMoveEvent(). Because it seemed to belong here (why not allow
5361        piece drops in training games?), and because it can only be
5362        performed after it is known to what we promote. */
5363     if (gameMode == Training) {
5364       /* compare the move played on the board to the next move in the
5365        * game. If they match, display the move and the opponent's response. 
5366        * If they don't match, display an error message.
5367        */
5368       int saveAnimate;
5369       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5370       CopyBoard(testBoard, boards[currentMove]);
5371       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5372
5373       if (CompareBoards(testBoard, boards[currentMove+1])) {
5374         ForwardInner(currentMove+1);
5375
5376         /* Autoplay the opponent's response.
5377          * if appData.animate was TRUE when Training mode was entered,
5378          * the response will be animated.
5379          */
5380         saveAnimate = appData.animate;
5381         appData.animate = animateTraining;
5382         ForwardInner(currentMove+1);
5383         appData.animate = saveAnimate;
5384
5385         /* check for the end of the game */
5386         if (currentMove >= forwardMostMove) {
5387           gameMode = PlayFromGameFile;
5388           ModeHighlight();
5389           SetTrainingModeOff();
5390           DisplayInformation(_("End of game"));
5391         }
5392       } else {
5393         DisplayError(_("Incorrect move"), 0);
5394       }
5395       return 1;
5396     }
5397
5398   /* Ok, now we know that the move is good, so we can kill
5399      the previous line in Analysis Mode */
5400   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5401     forwardMostMove = currentMove;
5402   }
5403
5404   /* If we need the chess program but it's dead, restart it */
5405   ResurrectChessProgram();
5406
5407   /* A user move restarts a paused game*/
5408   if (pausing)
5409     PauseEvent();
5410
5411   thinkOutput[0] = NULLCHAR;
5412
5413   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5414
5415   if (gameMode == BeginningOfGame) {
5416     if (appData.noChessProgram) {
5417       gameMode = EditGame;
5418       SetGameInfo();
5419     } else {
5420       char buf[MSG_SIZ];
5421       gameMode = MachinePlaysBlack;
5422       StartClocks();
5423       SetGameInfo();
5424       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5425       DisplayTitle(buf);
5426       if (first.sendName) {
5427         sprintf(buf, "name %s\n", gameInfo.white);
5428         SendToProgram(buf, &first);
5429       }
5430       StartClocks();
5431     }
5432     ModeHighlight();
5433   }
5434
5435   /* Relay move to ICS or chess engine */
5436   if (appData.icsActive) {
5437     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5438         gameMode == IcsExamining) {
5439       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5440       ics_user_moved = 1;
5441     }
5442   } else {
5443     if (first.sendTime && (gameMode == BeginningOfGame ||
5444                            gameMode == MachinePlaysWhite ||
5445                            gameMode == MachinePlaysBlack)) {
5446       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5447     }
5448     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5449          // [HGM] book: if program might be playing, let it use book
5450         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5451         first.maybeThinking = TRUE;
5452     } else SendMoveToProgram(forwardMostMove-1, &first);
5453     if (currentMove == cmailOldMove + 1) {
5454       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5455     }
5456   }
5457
5458   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5459
5460   switch (gameMode) {
5461   case EditGame:
5462     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5463                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5464     case MT_NONE:
5465     case MT_CHECK:
5466       break;
5467     case MT_CHECKMATE:
5468     case MT_STAINMATE:
5469       if (WhiteOnMove(currentMove)) {
5470         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5471       } else {
5472         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5473       }
5474       break;
5475     case MT_STALEMATE:
5476       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5477       break;
5478     }
5479     break;
5480     
5481   case MachinePlaysBlack:
5482   case MachinePlaysWhite:
5483     /* disable certain menu options while machine is thinking */
5484     SetMachineThinkingEnables();
5485     break;
5486
5487   default:
5488     break;
5489   }
5490
5491   if(bookHit) { // [HGM] book: simulate book reply
5492         static char bookMove[MSG_SIZ]; // a bit generous?
5493
5494         programStats.nodes = programStats.depth = programStats.time = 
5495         programStats.score = programStats.got_only_move = 0;
5496         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5497
5498         strcpy(bookMove, "move ");
5499         strcat(bookMove, bookHit);
5500         HandleMachineMove(bookMove, &first);
5501   }
5502   return 1;
5503 }
5504
5505 void
5506 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5507      int fromX, fromY, toX, toY;
5508      int promoChar;
5509 {
5510     /* [HGM] This routine was added to allow calling of its two logical
5511        parts from other modules in the old way. Before, UserMoveEvent()
5512        automatically called FinishMove() if the move was OK, and returned
5513        otherwise. I separated the two, in order to make it possible to
5514        slip a promotion popup in between. But that it always needs two
5515        calls, to the first part, (now called UserMoveTest() ), and to
5516        FinishMove if the first part succeeded. Calls that do not need
5517        to do anything in between, can call this routine the old way. 
5518     */
5519     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5520 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5521     if(moveType == AmbiguousMove)
5522         DrawPosition(FALSE, boards[currentMove]);
5523     else if(moveType != ImpossibleMove && moveType != Comment)
5524         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5525 }
5526
5527 void LeftClick(ClickType clickType, int xPix, int yPix)
5528 {
5529     int x, y;
5530     Boolean saveAnimate;
5531     static int second = 0, promotionChoice = 0;
5532     char promoChoice = NULLCHAR;
5533
5534     if (clickType == Press) ErrorPopDown();
5535
5536     x = EventToSquare(xPix, BOARD_WIDTH);
5537     y = EventToSquare(yPix, BOARD_HEIGHT);
5538     if (!flipView && y >= 0) {
5539         y = BOARD_HEIGHT - 1 - y;
5540     }
5541     if (flipView && x >= 0) {
5542         x = BOARD_WIDTH - 1 - x;
5543     }
5544
5545     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5546         if(clickType == Release) return; // ignore upclick of click-click destination
5547         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5548         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5549         if(gameInfo.holdingsWidth && 
5550                 (WhiteOnMove(currentMove) 
5551                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5552                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5553             // click in right holdings, for determining promotion piece
5554             ChessSquare p = boards[currentMove][y][x];
5555             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5556             if(p != EmptySquare) {
5557                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5558                 fromX = fromY = -1;
5559                 return;
5560             }
5561         }
5562         DrawPosition(FALSE, boards[currentMove]);
5563         return;
5564     }
5565
5566     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5567     if(clickType == Press
5568             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5569               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5570               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5571         return;
5572
5573     if (fromX == -1) {
5574         if (clickType == Press) {
5575             /* First square */
5576             if (OKToStartUserMove(x, y)) {
5577                 fromX = x;
5578                 fromY = y;
5579                 second = 0;
5580                 DragPieceBegin(xPix, yPix);
5581                 if (appData.highlightDragging) {
5582                     SetHighlights(x, y, -1, -1);
5583                 }
5584             }
5585         }
5586         return;
5587     }
5588
5589     /* fromX != -1 */
5590     if (clickType == Press && gameMode != EditPosition) {
5591         ChessSquare fromP;
5592         ChessSquare toP;
5593         int frc;
5594
5595         // ignore off-board to clicks
5596         if(y < 0 || x < 0) return;
5597
5598         /* Check if clicking again on the same color piece */
5599         fromP = boards[currentMove][fromY][fromX];
5600         toP = boards[currentMove][y][x];
5601         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5602         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5603              WhitePawn <= toP && toP <= WhiteKing &&
5604              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5605              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5606             (BlackPawn <= fromP && fromP <= BlackKing && 
5607              BlackPawn <= toP && toP <= BlackKing &&
5608              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5609              !(fromP == BlackKing && toP == BlackRook && frc))) {
5610             /* Clicked again on same color piece -- changed his mind */
5611             second = (x == fromX && y == fromY);
5612             if (appData.highlightDragging) {
5613                 SetHighlights(x, y, -1, -1);
5614             } else {
5615                 ClearHighlights();
5616             }
5617             if (OKToStartUserMove(x, y)) {
5618                 fromX = x;
5619                 fromY = y;
5620                 DragPieceBegin(xPix, yPix);
5621             }
5622             return;
5623         }
5624         // ignore clicks on holdings
5625         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5626     }
5627
5628     if (clickType == Release && x == fromX && y == fromY) {
5629         DragPieceEnd(xPix, yPix);
5630         if (appData.animateDragging) {
5631             /* Undo animation damage if any */
5632             DrawPosition(FALSE, NULL);
5633         }
5634         if (second) {
5635             /* Second up/down in same square; just abort move */
5636             second = 0;
5637             fromX = fromY = -1;
5638             ClearHighlights();
5639             gotPremove = 0;
5640             ClearPremoveHighlights();
5641         } else {
5642             /* First upclick in same square; start click-click mode */
5643             SetHighlights(x, y, -1, -1);
5644         }
5645         return;
5646     }
5647
5648     /* we now have a different from- and (possibly off-board) to-square */
5649     /* Completed move */
5650     toX = x;
5651     toY = y;
5652     saveAnimate = appData.animate;
5653     if (clickType == Press) {
5654         /* Finish clickclick move */
5655         if (appData.animate || appData.highlightLastMove) {
5656             SetHighlights(fromX, fromY, toX, toY);
5657         } else {
5658             ClearHighlights();
5659         }
5660     } else {
5661         /* Finish drag move */
5662         if (appData.highlightLastMove) {
5663             SetHighlights(fromX, fromY, toX, toY);
5664         } else {
5665             ClearHighlights();
5666         }
5667         DragPieceEnd(xPix, yPix);
5668         /* Don't animate move and drag both */
5669         appData.animate = FALSE;
5670     }
5671
5672     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5673     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5674         ClearHighlights();
5675         fromX = fromY = -1;
5676         DrawPosition(TRUE, NULL);
5677         return;
5678     }
5679
5680     // off-board moves should not be highlighted
5681     if(x < 0 || x < 0) ClearHighlights();
5682
5683     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5684         SetHighlights(fromX, fromY, toX, toY);
5685         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5686             // [HGM] super: promotion to captured piece selected from holdings
5687             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5688             promotionChoice = TRUE;
5689             // kludge follows to temporarily execute move on display, without promoting yet
5690             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5691             boards[currentMove][toY][toX] = p;
5692             DrawPosition(FALSE, boards[currentMove]);
5693             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5694             boards[currentMove][toY][toX] = q;
5695             DisplayMessage("Click in holdings to choose piece", "");
5696             return;
5697         }
5698         PromotionPopUp();
5699     } else {
5700         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5701         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5702         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5703         fromX = fromY = -1;
5704     }
5705     appData.animate = saveAnimate;
5706     if (appData.animate || appData.animateDragging) {
5707         /* Undo animation damage if needed */
5708         DrawPosition(FALSE, NULL);
5709     }
5710 }
5711
5712 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5713 {
5714 //    char * hint = lastHint;
5715     FrontEndProgramStats stats;
5716
5717     stats.which = cps == &first ? 0 : 1;
5718     stats.depth = cpstats->depth;
5719     stats.nodes = cpstats->nodes;
5720     stats.score = cpstats->score;
5721     stats.time = cpstats->time;
5722     stats.pv = cpstats->movelist;
5723     stats.hint = lastHint;
5724     stats.an_move_index = 0;
5725     stats.an_move_count = 0;
5726
5727     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5728         stats.hint = cpstats->move_name;
5729         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5730         stats.an_move_count = cpstats->nr_moves;
5731     }
5732
5733     SetProgramStats( &stats );
5734 }
5735
5736 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5737 {   // [HGM] book: this routine intercepts moves to simulate book replies
5738     char *bookHit = NULL;
5739
5740     //first determine if the incoming move brings opponent into his book
5741     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5742         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5743     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5744     if(bookHit != NULL && !cps->bookSuspend) {
5745         // make sure opponent is not going to reply after receiving move to book position
5746         SendToProgram("force\n", cps);
5747         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5748     }
5749     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5750     // now arrange restart after book miss
5751     if(bookHit) {
5752         // after a book hit we never send 'go', and the code after the call to this routine
5753         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5754         char buf[MSG_SIZ];
5755         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5756         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5757         SendToProgram(buf, cps);
5758         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5759     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5760         SendToProgram("go\n", cps);
5761         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5762     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5763         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5764             SendToProgram("go\n", cps); 
5765         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5766     }
5767     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5768 }
5769
5770 char *savedMessage;
5771 ChessProgramState *savedState;
5772 void DeferredBookMove(void)
5773 {
5774         if(savedState->lastPing != savedState->lastPong)
5775                     ScheduleDelayedEvent(DeferredBookMove, 10);
5776         else
5777         HandleMachineMove(savedMessage, savedState);
5778 }
5779
5780 void
5781 HandleMachineMove(message, cps)
5782      char *message;
5783      ChessProgramState *cps;
5784 {
5785     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5786     char realname[MSG_SIZ];
5787     int fromX, fromY, toX, toY;
5788     ChessMove moveType;
5789     char promoChar;
5790     char *p;
5791     int machineWhite;
5792     char *bookHit;
5793
5794 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5795     /*
5796      * Kludge to ignore BEL characters
5797      */
5798     while (*message == '\007') message++;
5799
5800     /*
5801      * [HGM] engine debug message: ignore lines starting with '#' character
5802      */
5803     if(cps->debug && *message == '#') return;
5804
5805     /*
5806      * Look for book output
5807      */
5808     if (cps == &first && bookRequested) {
5809         if (message[0] == '\t' || message[0] == ' ') {
5810             /* Part of the book output is here; append it */
5811             strcat(bookOutput, message);
5812             strcat(bookOutput, "  \n");
5813             return;
5814         } else if (bookOutput[0] != NULLCHAR) {
5815             /* All of book output has arrived; display it */
5816             char *p = bookOutput;
5817             while (*p != NULLCHAR) {
5818                 if (*p == '\t') *p = ' ';
5819                 p++;
5820             }
5821             DisplayInformation(bookOutput);
5822             bookRequested = FALSE;
5823             /* Fall through to parse the current output */
5824         }
5825     }
5826
5827     /*
5828      * Look for machine move.
5829      */
5830     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5831         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5832     {
5833         /* This method is only useful on engines that support ping */
5834         if (cps->lastPing != cps->lastPong) {
5835           if (gameMode == BeginningOfGame) {
5836             /* Extra move from before last new; ignore */
5837             if (appData.debugMode) {
5838                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5839             }
5840           } else {
5841             if (appData.debugMode) {
5842                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5843                         cps->which, gameMode);
5844             }
5845
5846             SendToProgram("undo\n", cps);
5847           }
5848           return;
5849         }
5850
5851         switch (gameMode) {
5852           case BeginningOfGame:
5853             /* Extra move from before last reset; ignore */
5854             if (appData.debugMode) {
5855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5856             }
5857             return;
5858
5859           case EndOfGame:
5860           case IcsIdle:
5861           default:
5862             /* Extra move after we tried to stop.  The mode test is
5863                not a reliable way of detecting this problem, but it's
5864                the best we can do on engines that don't support ping.
5865             */
5866             if (appData.debugMode) {
5867                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5868                         cps->which, gameMode);
5869             }
5870             SendToProgram("undo\n", cps);
5871             return;
5872
5873           case MachinePlaysWhite:
5874           case IcsPlayingWhite:
5875             machineWhite = TRUE;
5876             break;
5877
5878           case MachinePlaysBlack:
5879           case IcsPlayingBlack:
5880             machineWhite = FALSE;
5881             break;
5882
5883           case TwoMachinesPlay:
5884             machineWhite = (cps->twoMachinesColor[0] == 'w');
5885             break;
5886         }
5887         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5888             if (appData.debugMode) {
5889                 fprintf(debugFP,
5890                         "Ignoring move out of turn by %s, gameMode %d"
5891                         ", forwardMost %d\n",
5892                         cps->which, gameMode, forwardMostMove);
5893             }
5894             return;
5895         }
5896
5897     if (appData.debugMode) { int f = forwardMostMove;
5898         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5899                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5900     }
5901         if(cps->alphaRank) AlphaRank(machineMove, 4);
5902         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5903                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5904             /* Machine move could not be parsed; ignore it. */
5905             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5906                     machineMove, cps->which);
5907             DisplayError(buf1, 0);
5908             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5909                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5910             if (gameMode == TwoMachinesPlay) {
5911               GameEnds(machineWhite ? BlackWins : WhiteWins,
5912                        buf1, GE_XBOARD);
5913             }
5914             return;
5915         }
5916
5917         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5918         /* So we have to redo legality test with true e.p. status here,  */
5919         /* to make sure an illegal e.p. capture does not slip through,   */
5920         /* to cause a forfeit on a justified illegal-move complaint      */
5921         /* of the opponent.                                              */
5922         if( gameMode==TwoMachinesPlay && appData.testLegality
5923             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5924                                                               ) {
5925            ChessMove moveType;
5926            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5927                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5928                              fromY, fromX, toY, toX, promoChar);
5929             if (appData.debugMode) {
5930                 int i;
5931                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5932                     castlingRights[forwardMostMove][i], castlingRank[i]);
5933                 fprintf(debugFP, "castling rights\n");
5934             }
5935             if(moveType == IllegalMove) {
5936                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5937                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5938                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5939                            buf1, GE_XBOARD);
5940                 return;
5941            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5942            /* [HGM] Kludge to handle engines that send FRC-style castling
5943               when they shouldn't (like TSCP-Gothic) */
5944            switch(moveType) {
5945              case WhiteASideCastleFR:
5946              case BlackASideCastleFR:
5947                toX+=2;
5948                currentMoveString[2]++;
5949                break;
5950              case WhiteHSideCastleFR:
5951              case BlackHSideCastleFR:
5952                toX--;
5953                currentMoveString[2]--;
5954                break;
5955              default: ; // nothing to do, but suppresses warning of pedantic compilers
5956            }
5957         }
5958         hintRequested = FALSE;
5959         lastHint[0] = NULLCHAR;
5960         bookRequested = FALSE;
5961         /* Program may be pondering now */
5962         cps->maybeThinking = TRUE;
5963         if (cps->sendTime == 2) cps->sendTime = 1;
5964         if (cps->offeredDraw) cps->offeredDraw--;
5965
5966 #if ZIPPY
5967         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5968             first.initDone) {
5969           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5970           ics_user_moved = 1;
5971           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5972                 char buf[3*MSG_SIZ];
5973
5974                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5975                         programStats.score / 100.,
5976                         programStats.depth,
5977                         programStats.time / 100.,
5978                         (unsigned int)programStats.nodes,
5979                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5980                         programStats.movelist);
5981                 SendToICS(buf);
5982 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5983           }
5984         }
5985 #endif
5986         /* currentMoveString is set as a side-effect of ParseOneMove */
5987         strcpy(machineMove, currentMoveString);
5988         strcat(machineMove, "\n");
5989         strcpy(moveList[forwardMostMove], machineMove);
5990
5991         /* [AS] Save move info and clear stats for next move */
5992         pvInfoList[ forwardMostMove ].score = programStats.score;
5993         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5994         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5995         ClearProgramStats();
5996         thinkOutput[0] = NULLCHAR;
5997         hiddenThinkOutputState = 0;
5998
5999         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6000
6001         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6002         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6003             int count = 0;
6004
6005             while( count < adjudicateLossPlies ) {
6006                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6007
6008                 if( count & 1 ) {
6009                     score = -score; /* Flip score for winning side */
6010                 }
6011
6012                 if( score > adjudicateLossThreshold ) {
6013                     break;
6014                 }
6015
6016                 count++;
6017             }
6018
6019             if( count >= adjudicateLossPlies ) {
6020                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6021
6022                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6023                     "Xboard adjudication", 
6024                     GE_XBOARD );
6025
6026                 return;
6027             }
6028         }
6029
6030         if( gameMode == TwoMachinesPlay ) {
6031           // [HGM] some adjudications useful with buggy engines
6032             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6033           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6034
6035
6036             if( appData.testLegality )
6037             {   /* [HGM] Some more adjudications for obstinate engines */
6038                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6039                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6040                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6041                 static int moveCount = 6;
6042                 ChessMove result;
6043                 char *reason = NULL;
6044
6045                 /* Count what is on board. */
6046                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6047                 {   ChessSquare p = boards[forwardMostMove][i][j];
6048                     int m=i;
6049
6050                     switch((int) p)
6051                     {   /* count B,N,R and other of each side */
6052                         case WhiteKing:
6053                         case BlackKing:
6054                              NrK++; break; // [HGM] atomic: count Kings
6055                         case WhiteKnight:
6056                              NrWN++; break;
6057                         case WhiteBishop:
6058                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6059                              bishopsColor |= 1 << ((i^j)&1);
6060                              NrWB++; break;
6061                         case BlackKnight:
6062                              NrBN++; break;
6063                         case BlackBishop:
6064                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6065                              bishopsColor |= 1 << ((i^j)&1);
6066                              NrBB++; break;
6067                         case WhiteRook:
6068                              NrWR++; break;
6069                         case BlackRook:
6070                              NrBR++; break;
6071                         case WhiteQueen:
6072                              NrWQ++; break;
6073                         case BlackQueen:
6074                              NrBQ++; break;
6075                         case EmptySquare: 
6076                              break;
6077                         case BlackPawn:
6078                              m = 7-i;
6079                         case WhitePawn:
6080                              PawnAdvance += m; NrPawns++;
6081                     }
6082                     NrPieces += (p != EmptySquare);
6083                     NrW += ((int)p < (int)BlackPawn);
6084                     if(gameInfo.variant == VariantXiangqi && 
6085                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6086                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6087                         NrW -= ((int)p < (int)BlackPawn);
6088                     }
6089                 }
6090
6091                 /* Some material-based adjudications that have to be made before stalemate test */
6092                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6093                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6094                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6095                      if(appData.checkMates) {
6096                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6097                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6098                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6099                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6100                          return;
6101                      }
6102                 }
6103
6104                 /* Bare King in Shatranj (loses) or Losers (wins) */
6105                 if( NrW == 1 || NrPieces - NrW == 1) {
6106                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6107                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6108                      if(appData.checkMates) {
6109                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6110                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6111                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6112                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6113                          return;
6114                      }
6115                   } else
6116                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6117                   {    /* bare King */
6118                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6119                         if(appData.checkMates) {
6120                             /* but only adjudicate if adjudication enabled */
6121                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6122                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6123                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6124                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6125                             return;
6126                         }
6127                   }
6128                 } else bare = 1;
6129
6130
6131             // don't wait for engine to announce game end if we can judge ourselves
6132             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6133                                        castlingRights[forwardMostMove]) ) {
6134               case MT_CHECK:
6135                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6136                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6137                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6138                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6139                             checkCnt++;
6140                         if(checkCnt >= 2) {
6141                             reason = "Xboard adjudication: 3rd check";
6142                             epStatus[forwardMostMove] = EP_CHECKMATE;
6143                             break;
6144                         }
6145                     }
6146                 }
6147               case MT_NONE:
6148               default:
6149                 break;
6150               case MT_STALEMATE:
6151               case MT_STAINMATE:
6152                 reason = "Xboard adjudication: Stalemate";
6153                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6154                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6155                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6156                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6157                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6158                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6159                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6160                                                                         EP_CHECKMATE : EP_WINS);
6161                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6162                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6163                 }
6164                 break;
6165               case MT_CHECKMATE:
6166                 reason = "Xboard adjudication: Checkmate";
6167                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6168                 break;
6169             }
6170
6171                 switch(i = epStatus[forwardMostMove]) {
6172                     case EP_STALEMATE:
6173                         result = GameIsDrawn; break;
6174                     case EP_CHECKMATE:
6175                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6176                     case EP_WINS:
6177                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6178                     default:
6179                         result = (ChessMove) 0;
6180                 }
6181                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6182                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6183                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6184                     GameEnds( result, reason, GE_XBOARD );
6185                     return;
6186                 }
6187
6188                 /* Next absolutely insufficient mating material. */
6189                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6190                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6191                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6192                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6193                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6194
6195                      /* always flag draws, for judging claims */
6196                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6197
6198                      if(appData.materialDraws) {
6199                          /* but only adjudicate them if adjudication enabled */
6200                          SendToProgram("force\n", cps->other); // suppress reply
6201                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6202                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6203                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6204                          return;
6205                      }
6206                 }
6207
6208                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6209                 if(NrPieces == 4 && 
6210                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6211                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6212                    || NrWN==2 || NrBN==2     /* KNNK */
6213                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6214                   ) ) {
6215                      if(--moveCount < 0 && appData.trivialDraws)
6216                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6217                           SendToProgram("force\n", cps->other); // suppress reply
6218                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6219                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6220                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6221                           return;
6222                      }
6223                 } else moveCount = 6;
6224             }
6225           }
6226           
6227           if (appData.debugMode) { int i;
6228             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6229                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6230                     appData.drawRepeats);
6231             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6232               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6233             
6234           }
6235
6236                 /* Check for rep-draws */
6237                 count = 0;
6238                 for(k = forwardMostMove-2;
6239                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6240                         epStatus[k] < EP_UNKNOWN &&
6241                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6242                     k-=2)
6243                 {   int rights=0;
6244                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6245                         /* compare castling rights */
6246                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6247                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6248                                 rights++; /* King lost rights, while rook still had them */
6249                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6250                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6251                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6252                                    rights++; /* but at least one rook lost them */
6253                         }
6254                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6255                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6256                                 rights++; 
6257                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6258                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6259                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6260                                    rights++;
6261                         }
6262                         if( rights == 0 && ++count > appData.drawRepeats-2
6263                             && appData.drawRepeats > 1) {
6264                              /* adjudicate after user-specified nr of repeats */
6265                              SendToProgram("force\n", cps->other); // suppress reply
6266                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6267                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6268                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6269                                 // [HGM] xiangqi: check for forbidden perpetuals
6270                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6271                                 for(m=forwardMostMove; m>k; m-=2) {
6272                                     if(MateTest(boards[m], PosFlags(m), 
6273                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6274                                         ourPerpetual = 0; // the current mover did not always check
6275                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6276                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6277                                         hisPerpetual = 0; // the opponent did not always check
6278                                 }
6279                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6280                                                                         ourPerpetual, hisPerpetual);
6281                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6282                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6283                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6284                                     return;
6285                                 }
6286                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6287                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6288                                 // Now check for perpetual chases
6289                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6290                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6291                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6292                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6293                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6294                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6295                                         return;
6296                                     }
6297                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6298                                         break; // Abort repetition-checking loop.
6299                                 }
6300                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6301                              }
6302                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6303                              return;
6304                         }
6305                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6306                              epStatus[forwardMostMove] = EP_REP_DRAW;
6307                     }
6308                 }
6309
6310                 /* Now we test for 50-move draws. Determine ply count */
6311                 count = forwardMostMove;
6312                 /* look for last irreversble move */
6313                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6314                     count--;
6315                 /* if we hit starting position, add initial plies */
6316                 if( count == backwardMostMove )
6317                     count -= initialRulePlies;
6318                 count = forwardMostMove - count; 
6319                 if( count >= 100)
6320                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6321                          /* this is used to judge if draw claims are legal */
6322                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6323                          SendToProgram("force\n", cps->other); // suppress reply
6324                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6325                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6326                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6327                          return;
6328                 }
6329
6330                 /* if draw offer is pending, treat it as a draw claim
6331                  * when draw condition present, to allow engines a way to
6332                  * claim draws before making their move to avoid a race
6333                  * condition occurring after their move
6334                  */
6335                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6336                          char *p = NULL;
6337                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6338                              p = "Draw claim: 50-move rule";
6339                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6340                              p = "Draw claim: 3-fold repetition";
6341                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6342                              p = "Draw claim: insufficient mating material";
6343                          if( p != NULL ) {
6344                              SendToProgram("force\n", cps->other); // suppress reply
6345                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6346                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6347                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6348                              return;
6349                          }
6350                 }
6351
6352
6353                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6354                     SendToProgram("force\n", cps->other); // suppress reply
6355                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6356                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6357
6358                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6359
6360                     return;
6361                 }
6362         }
6363
6364         bookHit = NULL;
6365         if (gameMode == TwoMachinesPlay) {
6366             /* [HGM] relaying draw offers moved to after reception of move */
6367             /* and interpreting offer as claim if it brings draw condition */
6368             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6369                 SendToProgram("draw\n", cps->other);
6370             }
6371             if (cps->other->sendTime) {
6372                 SendTimeRemaining(cps->other,
6373                                   cps->other->twoMachinesColor[0] == 'w');
6374             }
6375             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6376             if (firstMove && !bookHit) {
6377                 firstMove = FALSE;
6378                 if (cps->other->useColors) {
6379                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6380                 }
6381                 SendToProgram("go\n", cps->other);
6382             }
6383             cps->other->maybeThinking = TRUE;
6384         }
6385
6386         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6387         
6388         if (!pausing && appData.ringBellAfterMoves) {
6389             RingBell();
6390         }
6391
6392         /* 
6393          * Reenable menu items that were disabled while
6394          * machine was thinking
6395          */
6396         if (gameMode != TwoMachinesPlay)
6397             SetUserThinkingEnables();
6398
6399         // [HGM] book: after book hit opponent has received move and is now in force mode
6400         // force the book reply into it, and then fake that it outputted this move by jumping
6401         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6402         if(bookHit) {
6403                 static char bookMove[MSG_SIZ]; // a bit generous?
6404
6405                 strcpy(bookMove, "move ");
6406                 strcat(bookMove, bookHit);
6407                 message = bookMove;
6408                 cps = cps->other;
6409                 programStats.nodes = programStats.depth = programStats.time = 
6410                 programStats.score = programStats.got_only_move = 0;
6411                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6412
6413                 if(cps->lastPing != cps->lastPong) {
6414                     savedMessage = message; // args for deferred call
6415                     savedState = cps;
6416                     ScheduleDelayedEvent(DeferredBookMove, 10);
6417                     return;
6418                 }
6419                 goto FakeBookMove;
6420         }
6421
6422         return;
6423     }
6424
6425     /* Set special modes for chess engines.  Later something general
6426      *  could be added here; for now there is just one kludge feature,
6427      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6428      *  when "xboard" is given as an interactive command.
6429      */
6430     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6431         cps->useSigint = FALSE;
6432         cps->useSigterm = FALSE;
6433     }
6434     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6435       ParseFeatures(message+8, cps);
6436       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6437     }
6438
6439     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6440      * want this, I was asked to put it in, and obliged.
6441      */
6442     if (!strncmp(message, "setboard ", 9)) {
6443         Board initial_position; int i;
6444
6445         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6446
6447         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6448             DisplayError(_("Bad FEN received from engine"), 0);
6449             return ;
6450         } else {
6451            Reset(TRUE, FALSE);
6452            CopyBoard(boards[0], initial_position);
6453            initialRulePlies = FENrulePlies;
6454            epStatus[0] = FENepStatus;
6455            for( i=0; i<nrCastlingRights; i++ )
6456                 castlingRights[0][i] = FENcastlingRights[i];
6457            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6458            else gameMode = MachinePlaysBlack;                 
6459            DrawPosition(FALSE, boards[currentMove]);
6460         }
6461         return;
6462     }
6463
6464     /*
6465      * Look for communication commands
6466      */
6467     if (!strncmp(message, "telluser ", 9)) {
6468         DisplayNote(message + 9);
6469         return;
6470     }
6471     if (!strncmp(message, "tellusererror ", 14)) {
6472         DisplayError(message + 14, 0);
6473         return;
6474     }
6475     if (!strncmp(message, "tellopponent ", 13)) {
6476       if (appData.icsActive) {
6477         if (loggedOn) {
6478           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6479           SendToICS(buf1);
6480         }
6481       } else {
6482         DisplayNote(message + 13);
6483       }
6484       return;
6485     }
6486     if (!strncmp(message, "tellothers ", 11)) {
6487       if (appData.icsActive) {
6488         if (loggedOn) {
6489           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6490           SendToICS(buf1);
6491         }
6492       }
6493       return;
6494     }
6495     if (!strncmp(message, "tellall ", 8)) {
6496       if (appData.icsActive) {
6497         if (loggedOn) {
6498           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6499           SendToICS(buf1);
6500         }
6501       } else {
6502         DisplayNote(message + 8);
6503       }
6504       return;
6505     }
6506     if (strncmp(message, "warning", 7) == 0) {
6507         /* Undocumented feature, use tellusererror in new code */
6508         DisplayError(message, 0);
6509         return;
6510     }
6511     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6512         strcpy(realname, cps->tidy);
6513         strcat(realname, " query");
6514         AskQuestion(realname, buf2, buf1, cps->pr);
6515         return;
6516     }
6517     /* Commands from the engine directly to ICS.  We don't allow these to be 
6518      *  sent until we are logged on. Crafty kibitzes have been known to 
6519      *  interfere with the login process.
6520      */
6521     if (loggedOn) {
6522         if (!strncmp(message, "tellics ", 8)) {
6523             SendToICS(message + 8);
6524             SendToICS("\n");
6525             return;
6526         }
6527         if (!strncmp(message, "tellicsnoalias ", 15)) {
6528             SendToICS(ics_prefix);
6529             SendToICS(message + 15);
6530             SendToICS("\n");
6531             return;
6532         }
6533         /* The following are for backward compatibility only */
6534         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6535             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6536             SendToICS(ics_prefix);
6537             SendToICS(message);
6538             SendToICS("\n");
6539             return;
6540         }
6541     }
6542     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6543         return;
6544     }
6545     /*
6546      * If the move is illegal, cancel it and redraw the board.
6547      * Also deal with other error cases.  Matching is rather loose
6548      * here to accommodate engines written before the spec.
6549      */
6550     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6551         strncmp(message, "Error", 5) == 0) {
6552         if (StrStr(message, "name") || 
6553             StrStr(message, "rating") || StrStr(message, "?") ||
6554             StrStr(message, "result") || StrStr(message, "board") ||
6555             StrStr(message, "bk") || StrStr(message, "computer") ||
6556             StrStr(message, "variant") || StrStr(message, "hint") ||
6557             StrStr(message, "random") || StrStr(message, "depth") ||
6558             StrStr(message, "accepted")) {
6559             return;
6560         }
6561         if (StrStr(message, "protover")) {
6562           /* Program is responding to input, so it's apparently done
6563              initializing, and this error message indicates it is
6564              protocol version 1.  So we don't need to wait any longer
6565              for it to initialize and send feature commands. */
6566           FeatureDone(cps, 1);
6567           cps->protocolVersion = 1;
6568           return;
6569         }
6570         cps->maybeThinking = FALSE;
6571
6572         if (StrStr(message, "draw")) {
6573             /* Program doesn't have "draw" command */
6574             cps->sendDrawOffers = 0;
6575             return;
6576         }
6577         if (cps->sendTime != 1 &&
6578             (StrStr(message, "time") || StrStr(message, "otim"))) {
6579           /* Program apparently doesn't have "time" or "otim" command */
6580           cps->sendTime = 0;
6581           return;
6582         }
6583         if (StrStr(message, "analyze")) {
6584             cps->analysisSupport = FALSE;
6585             cps->analyzing = FALSE;
6586             Reset(FALSE, TRUE);
6587             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6588             DisplayError(buf2, 0);
6589             return;
6590         }
6591         if (StrStr(message, "(no matching move)st")) {
6592           /* Special kludge for GNU Chess 4 only */
6593           cps->stKludge = TRUE;
6594           SendTimeControl(cps, movesPerSession, timeControl,
6595                           timeIncrement, appData.searchDepth,
6596                           searchTime);
6597           return;
6598         }
6599         if (StrStr(message, "(no matching move)sd")) {
6600           /* Special kludge for GNU Chess 4 only */
6601           cps->sdKludge = TRUE;
6602           SendTimeControl(cps, movesPerSession, timeControl,
6603                           timeIncrement, appData.searchDepth,
6604                           searchTime);
6605           return;
6606         }
6607         if (!StrStr(message, "llegal")) {
6608             return;
6609         }
6610         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6611             gameMode == IcsIdle) return;
6612         if (forwardMostMove <= backwardMostMove) return;
6613         if (pausing) PauseEvent();
6614       if(appData.forceIllegal) {
6615             // [HGM] illegal: machine refused move; force position after move into it
6616           SendToProgram("force\n", cps);
6617           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6618                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6619                 // when black is to move, while there might be nothing on a2 or black
6620                 // might already have the move. So send the board as if white has the move.
6621                 // But first we must change the stm of the engine, as it refused the last move
6622                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6623                 if(WhiteOnMove(forwardMostMove)) {
6624                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6625                     SendBoard(cps, forwardMostMove); // kludgeless board
6626                 } else {
6627                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6628                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6629                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6630                 }
6631           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6632             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6633                  gameMode == TwoMachinesPlay)
6634               SendToProgram("go\n", cps);
6635             return;
6636       } else
6637         if (gameMode == PlayFromGameFile) {
6638             /* Stop reading this game file */
6639             gameMode = EditGame;
6640             ModeHighlight();
6641         }
6642         currentMove = --forwardMostMove;
6643         DisplayMove(currentMove-1); /* before DisplayMoveError */
6644         SwitchClocks();
6645         DisplayBothClocks();
6646         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6647                 parseList[currentMove], cps->which);
6648         DisplayMoveError(buf1);
6649         DrawPosition(FALSE, boards[currentMove]);
6650
6651         /* [HGM] illegal-move claim should forfeit game when Xboard */
6652         /* only passes fully legal moves                            */
6653         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6654             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6655                                 "False illegal-move claim", GE_XBOARD );
6656         }
6657         return;
6658     }
6659     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6660         /* Program has a broken "time" command that
6661            outputs a string not ending in newline.
6662            Don't use it. */
6663         cps->sendTime = 0;
6664     }
6665     
6666     /*
6667      * If chess program startup fails, exit with an error message.
6668      * Attempts to recover here are futile.
6669      */
6670     if ((StrStr(message, "unknown host") != NULL)
6671         || (StrStr(message, "No remote directory") != NULL)
6672         || (StrStr(message, "not found") != NULL)
6673         || (StrStr(message, "No such file") != NULL)
6674         || (StrStr(message, "can't alloc") != NULL)
6675         || (StrStr(message, "Permission denied") != NULL)) {
6676
6677         cps->maybeThinking = FALSE;
6678         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6679                 cps->which, cps->program, cps->host, message);
6680         RemoveInputSource(cps->isr);
6681         DisplayFatalError(buf1, 0, 1);
6682         return;
6683     }
6684     
6685     /* 
6686      * Look for hint output
6687      */
6688     if (sscanf(message, "Hint: %s", buf1) == 1) {
6689         if (cps == &first && hintRequested) {
6690             hintRequested = FALSE;
6691             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6692                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6693                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6694                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6695                                     fromY, fromX, toY, toX, promoChar, buf1);
6696                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6697                 DisplayInformation(buf2);
6698             } else {
6699                 /* Hint move could not be parsed!? */
6700               snprintf(buf2, sizeof(buf2),
6701                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6702                         buf1, cps->which);
6703                 DisplayError(buf2, 0);
6704             }
6705         } else {
6706             strcpy(lastHint, buf1);
6707         }
6708         return;
6709     }
6710
6711     /*
6712      * Ignore other messages if game is not in progress
6713      */
6714     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6715         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6716
6717     /*
6718      * look for win, lose, draw, or draw offer
6719      */
6720     if (strncmp(message, "1-0", 3) == 0) {
6721         char *p, *q, *r = "";
6722         p = strchr(message, '{');
6723         if (p) {
6724             q = strchr(p, '}');
6725             if (q) {
6726                 *q = NULLCHAR;
6727                 r = p + 1;
6728             }
6729         }
6730         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6731         return;
6732     } else if (strncmp(message, "0-1", 3) == 0) {
6733         char *p, *q, *r = "";
6734         p = strchr(message, '{');
6735         if (p) {
6736             q = strchr(p, '}');
6737             if (q) {
6738                 *q = NULLCHAR;
6739                 r = p + 1;
6740             }
6741         }
6742         /* Kludge for Arasan 4.1 bug */
6743         if (strcmp(r, "Black resigns") == 0) {
6744             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6745             return;
6746         }
6747         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6748         return;
6749     } else if (strncmp(message, "1/2", 3) == 0) {
6750         char *p, *q, *r = "";
6751         p = strchr(message, '{');
6752         if (p) {
6753             q = strchr(p, '}');
6754             if (q) {
6755                 *q = NULLCHAR;
6756                 r = p + 1;
6757             }
6758         }
6759             
6760         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6761         return;
6762
6763     } else if (strncmp(message, "White resign", 12) == 0) {
6764         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6765         return;
6766     } else if (strncmp(message, "Black resign", 12) == 0) {
6767         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6768         return;
6769     } else if (strncmp(message, "White matches", 13) == 0 ||
6770                strncmp(message, "Black matches", 13) == 0   ) {
6771         /* [HGM] ignore GNUShogi noises */
6772         return;
6773     } else if (strncmp(message, "White", 5) == 0 &&
6774                message[5] != '(' &&
6775                StrStr(message, "Black") == NULL) {
6776         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6777         return;
6778     } else if (strncmp(message, "Black", 5) == 0 &&
6779                message[5] != '(') {
6780         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6781         return;
6782     } else if (strcmp(message, "resign") == 0 ||
6783                strcmp(message, "computer resigns") == 0) {
6784         switch (gameMode) {
6785           case MachinePlaysBlack:
6786           case IcsPlayingBlack:
6787             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6788             break;
6789           case MachinePlaysWhite:
6790           case IcsPlayingWhite:
6791             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6792             break;
6793           case TwoMachinesPlay:
6794             if (cps->twoMachinesColor[0] == 'w')
6795               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6796             else
6797               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6798             break;
6799           default:
6800             /* can't happen */
6801             break;
6802         }
6803         return;
6804     } else if (strncmp(message, "opponent mates", 14) == 0) {
6805         switch (gameMode) {
6806           case MachinePlaysBlack:
6807           case IcsPlayingBlack:
6808             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6809             break;
6810           case MachinePlaysWhite:
6811           case IcsPlayingWhite:
6812             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6813             break;
6814           case TwoMachinesPlay:
6815             if (cps->twoMachinesColor[0] == 'w')
6816               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6817             else
6818               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6819             break;
6820           default:
6821             /* can't happen */
6822             break;
6823         }
6824         return;
6825     } else if (strncmp(message, "computer mates", 14) == 0) {
6826         switch (gameMode) {
6827           case MachinePlaysBlack:
6828           case IcsPlayingBlack:
6829             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6830             break;
6831           case MachinePlaysWhite:
6832           case IcsPlayingWhite:
6833             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6834             break;
6835           case TwoMachinesPlay:
6836             if (cps->twoMachinesColor[0] == 'w')
6837               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6838             else
6839               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6840             break;
6841           default:
6842             /* can't happen */
6843             break;
6844         }
6845         return;
6846     } else if (strncmp(message, "checkmate", 9) == 0) {
6847         if (WhiteOnMove(forwardMostMove)) {
6848             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6849         } else {
6850             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6851         }
6852         return;
6853     } else if (strstr(message, "Draw") != NULL ||
6854                strstr(message, "game is a draw") != NULL) {
6855         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6856         return;
6857     } else if (strstr(message, "offer") != NULL &&
6858                strstr(message, "draw") != NULL) {
6859 #if ZIPPY
6860         if (appData.zippyPlay && first.initDone) {
6861             /* Relay offer to ICS */
6862             SendToICS(ics_prefix);
6863             SendToICS("draw\n");
6864         }
6865 #endif
6866         cps->offeredDraw = 2; /* valid until this engine moves twice */
6867         if (gameMode == TwoMachinesPlay) {
6868             if (cps->other->offeredDraw) {
6869                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6870             /* [HGM] in two-machine mode we delay relaying draw offer      */
6871             /* until after we also have move, to see if it is really claim */
6872             }
6873         } else if (gameMode == MachinePlaysWhite ||
6874                    gameMode == MachinePlaysBlack) {
6875           if (userOfferedDraw) {
6876             DisplayInformation(_("Machine accepts your draw offer"));
6877             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6878           } else {
6879             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6880           }
6881         }
6882     }
6883
6884     
6885     /*
6886      * Look for thinking output
6887      */
6888     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6889           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6890                                 ) {
6891         int plylev, mvleft, mvtot, curscore, time;
6892         char mvname[MOVE_LEN];
6893         u64 nodes; // [DM]
6894         char plyext;
6895         int ignore = FALSE;
6896         int prefixHint = FALSE;
6897         mvname[0] = NULLCHAR;
6898
6899         switch (gameMode) {
6900           case MachinePlaysBlack:
6901           case IcsPlayingBlack:
6902             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6903             break;
6904           case MachinePlaysWhite:
6905           case IcsPlayingWhite:
6906             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6907             break;
6908           case AnalyzeMode:
6909           case AnalyzeFile:
6910             break;
6911           case IcsObserving: /* [DM] icsEngineAnalyze */
6912             if (!appData.icsEngineAnalyze) ignore = TRUE;
6913             break;
6914           case TwoMachinesPlay:
6915             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6916                 ignore = TRUE;
6917             }
6918             break;
6919           default:
6920             ignore = TRUE;
6921             break;
6922         }
6923
6924         if (!ignore) {
6925             buf1[0] = NULLCHAR;
6926             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6927                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6928
6929                 if (plyext != ' ' && plyext != '\t') {
6930                     time *= 100;
6931                 }
6932
6933                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6934                 if( cps->scoreIsAbsolute && 
6935                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6936                 {
6937                     curscore = -curscore;
6938                 }
6939
6940
6941                 programStats.depth = plylev;
6942                 programStats.nodes = nodes;
6943                 programStats.time = time;
6944                 programStats.score = curscore;
6945                 programStats.got_only_move = 0;
6946
6947                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6948                         int ticklen;
6949
6950                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6951                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6952                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6953                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6954                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6955                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6956                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6957                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6958                 }
6959
6960                 /* Buffer overflow protection */
6961                 if (buf1[0] != NULLCHAR) {
6962                     if (strlen(buf1) >= sizeof(programStats.movelist)
6963                         && appData.debugMode) {
6964                         fprintf(debugFP,
6965                                 "PV is too long; using the first %u bytes.\n",
6966                                 (unsigned) sizeof(programStats.movelist) - 1);
6967                     }
6968
6969                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6970                 } else {
6971                     sprintf(programStats.movelist, " no PV\n");
6972                 }
6973
6974                 if (programStats.seen_stat) {
6975                     programStats.ok_to_send = 1;
6976                 }
6977
6978                 if (strchr(programStats.movelist, '(') != NULL) {
6979                     programStats.line_is_book = 1;
6980                     programStats.nr_moves = 0;
6981                     programStats.moves_left = 0;
6982                 } else {
6983                     programStats.line_is_book = 0;
6984                 }
6985
6986                 SendProgramStatsToFrontend( cps, &programStats );
6987
6988                 /* 
6989                     [AS] Protect the thinkOutput buffer from overflow... this
6990                     is only useful if buf1 hasn't overflowed first!
6991                 */
6992                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6993                         plylev, 
6994                         (gameMode == TwoMachinesPlay ?
6995                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6996                         ((double) curscore) / 100.0,
6997                         prefixHint ? lastHint : "",
6998                         prefixHint ? " " : "" );
6999
7000                 if( buf1[0] != NULLCHAR ) {
7001                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7002
7003                     if( strlen(buf1) > max_len ) {
7004                         if( appData.debugMode) {
7005                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7006                         }
7007                         buf1[max_len+1] = '\0';
7008                     }
7009
7010                     strcat( thinkOutput, buf1 );
7011                 }
7012
7013                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7014                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7015                     DisplayMove(currentMove - 1);
7016                 }
7017                 return;
7018
7019             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7020                 /* crafty (9.25+) says "(only move) <move>"
7021                  * if there is only 1 legal move
7022                  */
7023                 sscanf(p, "(only move) %s", buf1);
7024                 sprintf(thinkOutput, "%s (only move)", buf1);
7025                 sprintf(programStats.movelist, "%s (only move)", buf1);
7026                 programStats.depth = 1;
7027                 programStats.nr_moves = 1;
7028                 programStats.moves_left = 1;
7029                 programStats.nodes = 1;
7030                 programStats.time = 1;
7031                 programStats.got_only_move = 1;
7032
7033                 /* Not really, but we also use this member to
7034                    mean "line isn't going to change" (Crafty
7035                    isn't searching, so stats won't change) */
7036                 programStats.line_is_book = 1;
7037
7038                 SendProgramStatsToFrontend( cps, &programStats );
7039                 
7040                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7041                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7042                     DisplayMove(currentMove - 1);
7043                 }
7044                 return;
7045             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7046                               &time, &nodes, &plylev, &mvleft,
7047                               &mvtot, mvname) >= 5) {
7048                 /* The stat01: line is from Crafty (9.29+) in response
7049                    to the "." command */
7050                 programStats.seen_stat = 1;
7051                 cps->maybeThinking = TRUE;
7052
7053                 if (programStats.got_only_move || !appData.periodicUpdates)
7054                   return;
7055
7056                 programStats.depth = plylev;
7057                 programStats.time = time;
7058                 programStats.nodes = nodes;
7059                 programStats.moves_left = mvleft;
7060                 programStats.nr_moves = mvtot;
7061                 strcpy(programStats.move_name, mvname);
7062                 programStats.ok_to_send = 1;
7063                 programStats.movelist[0] = '\0';
7064
7065                 SendProgramStatsToFrontend( cps, &programStats );
7066
7067                 return;
7068
7069             } else if (strncmp(message,"++",2) == 0) {
7070                 /* Crafty 9.29+ outputs this */
7071                 programStats.got_fail = 2;
7072                 return;
7073
7074             } else if (strncmp(message,"--",2) == 0) {
7075                 /* Crafty 9.29+ outputs this */
7076                 programStats.got_fail = 1;
7077                 return;
7078
7079             } else if (thinkOutput[0] != NULLCHAR &&
7080                        strncmp(message, "    ", 4) == 0) {
7081                 unsigned message_len;
7082
7083                 p = message;
7084                 while (*p && *p == ' ') p++;
7085
7086                 message_len = strlen( p );
7087
7088                 /* [AS] Avoid buffer overflow */
7089                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7090                     strcat(thinkOutput, " ");
7091                     strcat(thinkOutput, p);
7092                 }
7093
7094                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7095                     strcat(programStats.movelist, " ");
7096                     strcat(programStats.movelist, p);
7097                 }
7098
7099                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7100                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7101                     DisplayMove(currentMove - 1);
7102                 }
7103                 return;
7104             }
7105         }
7106         else {
7107             buf1[0] = NULLCHAR;
7108
7109             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7110                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7111             {
7112                 ChessProgramStats cpstats;
7113
7114                 if (plyext != ' ' && plyext != '\t') {
7115                     time *= 100;
7116                 }
7117
7118                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7119                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7120                     curscore = -curscore;
7121                 }
7122
7123                 cpstats.depth = plylev;
7124                 cpstats.nodes = nodes;
7125                 cpstats.time = time;
7126                 cpstats.score = curscore;
7127                 cpstats.got_only_move = 0;
7128                 cpstats.movelist[0] = '\0';
7129
7130                 if (buf1[0] != NULLCHAR) {
7131                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7132                 }
7133
7134                 cpstats.ok_to_send = 0;
7135                 cpstats.line_is_book = 0;
7136                 cpstats.nr_moves = 0;
7137                 cpstats.moves_left = 0;
7138
7139                 SendProgramStatsToFrontend( cps, &cpstats );
7140             }
7141         }
7142     }
7143 }
7144
7145
7146 /* Parse a game score from the character string "game", and
7147    record it as the history of the current game.  The game
7148    score is NOT assumed to start from the standard position. 
7149    The display is not updated in any way.
7150    */
7151 void
7152 ParseGameHistory(game)
7153      char *game;
7154 {
7155     ChessMove moveType;
7156     int fromX, fromY, toX, toY, boardIndex;
7157     char promoChar;
7158     char *p, *q;
7159     char buf[MSG_SIZ];
7160
7161     if (appData.debugMode)
7162       fprintf(debugFP, "Parsing game history: %s\n", game);
7163
7164     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7165     gameInfo.site = StrSave(appData.icsHost);
7166     gameInfo.date = PGNDate();
7167     gameInfo.round = StrSave("-");
7168
7169     /* Parse out names of players */
7170     while (*game == ' ') game++;
7171     p = buf;
7172     while (*game != ' ') *p++ = *game++;
7173     *p = NULLCHAR;
7174     gameInfo.white = StrSave(buf);
7175     while (*game == ' ') game++;
7176     p = buf;
7177     while (*game != ' ' && *game != '\n') *p++ = *game++;
7178     *p = NULLCHAR;
7179     gameInfo.black = StrSave(buf);
7180
7181     /* Parse moves */
7182     boardIndex = blackPlaysFirst ? 1 : 0;
7183     yynewstr(game);
7184     for (;;) {
7185         yyboardindex = boardIndex;
7186         moveType = (ChessMove) yylex();
7187         switch (moveType) {
7188           case IllegalMove:             /* maybe suicide chess, etc. */
7189   if (appData.debugMode) {
7190     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7191     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7192     setbuf(debugFP, NULL);
7193   }
7194           case WhitePromotionChancellor:
7195           case BlackPromotionChancellor:
7196           case WhitePromotionArchbishop:
7197           case BlackPromotionArchbishop:
7198           case WhitePromotionQueen:
7199           case BlackPromotionQueen:
7200           case WhitePromotionRook:
7201           case BlackPromotionRook:
7202           case WhitePromotionBishop:
7203           case BlackPromotionBishop:
7204           case WhitePromotionKnight:
7205           case BlackPromotionKnight:
7206           case WhitePromotionKing:
7207           case BlackPromotionKing:
7208           case NormalMove:
7209           case WhiteCapturesEnPassant:
7210           case BlackCapturesEnPassant:
7211           case WhiteKingSideCastle:
7212           case WhiteQueenSideCastle:
7213           case BlackKingSideCastle:
7214           case BlackQueenSideCastle:
7215           case WhiteKingSideCastleWild:
7216           case WhiteQueenSideCastleWild:
7217           case BlackKingSideCastleWild:
7218           case BlackQueenSideCastleWild:
7219           /* PUSH Fabien */
7220           case WhiteHSideCastleFR:
7221           case WhiteASideCastleFR:
7222           case BlackHSideCastleFR:
7223           case BlackASideCastleFR:
7224           /* POP Fabien */
7225             fromX = currentMoveString[0] - AAA;
7226             fromY = currentMoveString[1] - ONE;
7227             toX = currentMoveString[2] - AAA;
7228             toY = currentMoveString[3] - ONE;
7229             promoChar = currentMoveString[4];
7230             break;
7231           case WhiteDrop:
7232           case BlackDrop:
7233             fromX = moveType == WhiteDrop ?
7234               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7235             (int) CharToPiece(ToLower(currentMoveString[0]));
7236             fromY = DROP_RANK;
7237             toX = currentMoveString[2] - AAA;
7238             toY = currentMoveString[3] - ONE;
7239             promoChar = NULLCHAR;
7240             break;
7241           case AmbiguousMove:
7242             /* bug? */
7243             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7244   if (appData.debugMode) {
7245     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247     setbuf(debugFP, NULL);
7248   }
7249             DisplayError(buf, 0);
7250             return;
7251           case ImpossibleMove:
7252             /* bug? */
7253             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7254   if (appData.debugMode) {
7255     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7256     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7257     setbuf(debugFP, NULL);
7258   }
7259             DisplayError(buf, 0);
7260             return;
7261           case (ChessMove) 0:   /* end of file */
7262             if (boardIndex < backwardMostMove) {
7263                 /* Oops, gap.  How did that happen? */
7264                 DisplayError(_("Gap in move list"), 0);
7265                 return;
7266             }
7267             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7268             if (boardIndex > forwardMostMove) {
7269                 forwardMostMove = boardIndex;
7270             }
7271             return;
7272           case ElapsedTime:
7273             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7274                 strcat(parseList[boardIndex-1], " ");
7275                 strcat(parseList[boardIndex-1], yy_text);
7276             }
7277             continue;
7278           case Comment:
7279           case PGNTag:
7280           case NAG:
7281           default:
7282             /* ignore */
7283             continue;
7284           case WhiteWins:
7285           case BlackWins:
7286           case GameIsDrawn:
7287           case GameUnfinished:
7288             if (gameMode == IcsExamining) {
7289                 if (boardIndex < backwardMostMove) {
7290                     /* Oops, gap.  How did that happen? */
7291                     return;
7292                 }
7293                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7294                 return;
7295             }
7296             gameInfo.result = moveType;
7297             p = strchr(yy_text, '{');
7298             if (p == NULL) p = strchr(yy_text, '(');
7299             if (p == NULL) {
7300                 p = yy_text;
7301                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7302             } else {
7303                 q = strchr(p, *p == '{' ? '}' : ')');
7304                 if (q != NULL) *q = NULLCHAR;
7305                 p++;
7306             }
7307             gameInfo.resultDetails = StrSave(p);
7308             continue;
7309         }
7310         if (boardIndex >= forwardMostMove &&
7311             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7312             backwardMostMove = blackPlaysFirst ? 1 : 0;
7313             return;
7314         }
7315         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7316                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7317                                  parseList[boardIndex]);
7318         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7319         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7320         /* currentMoveString is set as a side-effect of yylex */
7321         strcpy(moveList[boardIndex], currentMoveString);
7322         strcat(moveList[boardIndex], "\n");
7323         boardIndex++;
7324         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7325                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7326         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7327                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7328           case MT_NONE:
7329           case MT_STALEMATE:
7330           default:
7331             break;
7332           case MT_CHECK:
7333             if(gameInfo.variant != VariantShogi)
7334                 strcat(parseList[boardIndex - 1], "+");
7335             break;
7336           case MT_CHECKMATE:
7337           case MT_STAINMATE:
7338             strcat(parseList[boardIndex - 1], "#");
7339             break;
7340         }
7341     }
7342 }
7343
7344
7345 /* Apply a move to the given board  */
7346 void
7347 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7348      int fromX, fromY, toX, toY;
7349      int promoChar;
7350      Board board;
7351      char *castling;
7352      char *ep;
7353 {
7354   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7355
7356     /* [HGM] compute & store e.p. status and castling rights for new position */
7357     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7358     { int i;
7359
7360       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7361       oldEP = *ep;
7362       *ep = EP_NONE;
7363
7364       if( board[toY][toX] != EmptySquare ) 
7365            *ep = EP_CAPTURE;  
7366
7367       if( board[fromY][fromX] == WhitePawn ) {
7368            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7369                *ep = EP_PAWN_MOVE;
7370            if( toY-fromY==2) {
7371                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7372                         gameInfo.variant != VariantBerolina || toX < fromX)
7373                       *ep = toX | berolina;
7374                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7375                         gameInfo.variant != VariantBerolina || toX > fromX) 
7376                       *ep = toX;
7377            }
7378       } else 
7379       if( board[fromY][fromX] == BlackPawn ) {
7380            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7381                *ep = EP_PAWN_MOVE; 
7382            if( toY-fromY== -2) {
7383                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7384                         gameInfo.variant != VariantBerolina || toX < fromX)
7385                       *ep = toX | berolina;
7386                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7387                         gameInfo.variant != VariantBerolina || toX > fromX) 
7388                       *ep = toX;
7389            }
7390        }
7391
7392        for(i=0; i<nrCastlingRights; i++) {
7393            if(castling[i] == fromX && castlingRank[i] == fromY ||
7394               castling[i] == toX   && castlingRank[i] == toY   
7395              ) castling[i] = -1; // revoke for moved or captured piece
7396        }
7397
7398     }
7399
7400   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7401   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7402        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7403          
7404   if (fromX == toX && fromY == toY) return;
7405
7406   if (fromY == DROP_RANK) {
7407         /* must be first */
7408         piece = board[toY][toX] = (ChessSquare) fromX;
7409   } else {
7410      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7411      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7412      if(gameInfo.variant == VariantKnightmate)
7413          king += (int) WhiteUnicorn - (int) WhiteKing;
7414
7415     /* Code added by Tord: */
7416     /* FRC castling assumed when king captures friendly rook. */
7417     if (board[fromY][fromX] == WhiteKing &&
7418              board[toY][toX] == WhiteRook) {
7419       board[fromY][fromX] = EmptySquare;
7420       board[toY][toX] = EmptySquare;
7421       if(toX > fromX) {
7422         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7423       } else {
7424         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7425       }
7426     } else if (board[fromY][fromX] == BlackKing &&
7427                board[toY][toX] == BlackRook) {
7428       board[fromY][fromX] = EmptySquare;
7429       board[toY][toX] = EmptySquare;
7430       if(toX > fromX) {
7431         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7432       } else {
7433         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7434       }
7435     /* End of code added by Tord */
7436
7437     } else if (board[fromY][fromX] == king
7438         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7439         && toY == fromY && toX > fromX+1) {
7440         board[fromY][fromX] = EmptySquare;
7441         board[toY][toX] = king;
7442         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7443         board[fromY][BOARD_RGHT-1] = EmptySquare;
7444     } else if (board[fromY][fromX] == king
7445         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7446                && toY == fromY && toX < fromX-1) {
7447         board[fromY][fromX] = EmptySquare;
7448         board[toY][toX] = king;
7449         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7450         board[fromY][BOARD_LEFT] = EmptySquare;
7451     } else if (board[fromY][fromX] == WhitePawn
7452                && toY == BOARD_HEIGHT-1
7453                && gameInfo.variant != VariantXiangqi
7454                ) {
7455         /* white pawn promotion */
7456         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7457         if (board[toY][toX] == EmptySquare) {
7458             board[toY][toX] = WhiteQueen;
7459         }
7460         if(gameInfo.variant==VariantBughouse ||
7461            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7462             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7463         board[fromY][fromX] = EmptySquare;
7464     } else if ((fromY == BOARD_HEIGHT-4)
7465                && (toX != fromX)
7466                && gameInfo.variant != VariantXiangqi
7467                && gameInfo.variant != VariantBerolina
7468                && (board[fromY][fromX] == WhitePawn)
7469                && (board[toY][toX] == EmptySquare)) {
7470         board[fromY][fromX] = EmptySquare;
7471         board[toY][toX] = WhitePawn;
7472         captured = board[toY - 1][toX];
7473         board[toY - 1][toX] = EmptySquare;
7474     } else if ((fromY == BOARD_HEIGHT-4)
7475                && (toX == fromX)
7476                && gameInfo.variant == VariantBerolina
7477                && (board[fromY][fromX] == WhitePawn)
7478                && (board[toY][toX] == EmptySquare)) {
7479         board[fromY][fromX] = EmptySquare;
7480         board[toY][toX] = WhitePawn;
7481         if(oldEP & EP_BEROLIN_A) {
7482                 captured = board[fromY][fromX-1];
7483                 board[fromY][fromX-1] = EmptySquare;
7484         }else{  captured = board[fromY][fromX+1];
7485                 board[fromY][fromX+1] = EmptySquare;
7486         }
7487     } else if (board[fromY][fromX] == king
7488         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7489                && toY == fromY && toX > fromX+1) {
7490         board[fromY][fromX] = EmptySquare;
7491         board[toY][toX] = king;
7492         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7493         board[fromY][BOARD_RGHT-1] = EmptySquare;
7494     } else if (board[fromY][fromX] == king
7495         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7496                && toY == fromY && toX < fromX-1) {
7497         board[fromY][fromX] = EmptySquare;
7498         board[toY][toX] = king;
7499         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7500         board[fromY][BOARD_LEFT] = EmptySquare;
7501     } else if (fromY == 7 && fromX == 3
7502                && board[fromY][fromX] == BlackKing
7503                && toY == 7 && toX == 5) {
7504         board[fromY][fromX] = EmptySquare;
7505         board[toY][toX] = BlackKing;
7506         board[fromY][7] = EmptySquare;
7507         board[toY][4] = BlackRook;
7508     } else if (fromY == 7 && fromX == 3
7509                && board[fromY][fromX] == BlackKing
7510                && toY == 7 && toX == 1) {
7511         board[fromY][fromX] = EmptySquare;
7512         board[toY][toX] = BlackKing;
7513         board[fromY][0] = EmptySquare;
7514         board[toY][2] = BlackRook;
7515     } else if (board[fromY][fromX] == BlackPawn
7516                && toY == 0
7517                && gameInfo.variant != VariantXiangqi
7518                ) {
7519         /* black pawn promotion */
7520         board[0][toX] = CharToPiece(ToLower(promoChar));
7521         if (board[0][toX] == EmptySquare) {
7522             board[0][toX] = BlackQueen;
7523         }
7524         if(gameInfo.variant==VariantBughouse ||
7525            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7526             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7527         board[fromY][fromX] = EmptySquare;
7528     } else if ((fromY == 3)
7529                && (toX != fromX)
7530                && gameInfo.variant != VariantXiangqi
7531                && gameInfo.variant != VariantBerolina
7532                && (board[fromY][fromX] == BlackPawn)
7533                && (board[toY][toX] == EmptySquare)) {
7534         board[fromY][fromX] = EmptySquare;
7535         board[toY][toX] = BlackPawn;
7536         captured = board[toY + 1][toX];
7537         board[toY + 1][toX] = EmptySquare;
7538     } else if ((fromY == 3)
7539                && (toX == fromX)
7540                && gameInfo.variant == VariantBerolina
7541                && (board[fromY][fromX] == BlackPawn)
7542                && (board[toY][toX] == EmptySquare)) {
7543         board[fromY][fromX] = EmptySquare;
7544         board[toY][toX] = BlackPawn;
7545         if(oldEP & EP_BEROLIN_A) {
7546                 captured = board[fromY][fromX-1];
7547                 board[fromY][fromX-1] = EmptySquare;
7548         }else{  captured = board[fromY][fromX+1];
7549                 board[fromY][fromX+1] = EmptySquare;
7550         }
7551     } else {
7552         board[toY][toX] = board[fromY][fromX];
7553         board[fromY][fromX] = EmptySquare;
7554     }
7555
7556     /* [HGM] now we promote for Shogi, if needed */
7557     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7558         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7559   }
7560
7561     if (gameInfo.holdingsWidth != 0) {
7562
7563       /* !!A lot more code needs to be written to support holdings  */
7564       /* [HGM] OK, so I have written it. Holdings are stored in the */
7565       /* penultimate board files, so they are automaticlly stored   */
7566       /* in the game history.                                       */
7567       if (fromY == DROP_RANK) {
7568         /* Delete from holdings, by decreasing count */
7569         /* and erasing image if necessary            */
7570         p = (int) fromX;
7571         if(p < (int) BlackPawn) { /* white drop */
7572              p -= (int)WhitePawn;
7573                  p = PieceToNumber((ChessSquare)p);
7574              if(p >= gameInfo.holdingsSize) p = 0;
7575              if(--board[p][BOARD_WIDTH-2] <= 0)
7576                   board[p][BOARD_WIDTH-1] = EmptySquare;
7577              if((int)board[p][BOARD_WIDTH-2] < 0)
7578                         board[p][BOARD_WIDTH-2] = 0;
7579         } else {                  /* black drop */
7580              p -= (int)BlackPawn;
7581                  p = PieceToNumber((ChessSquare)p);
7582              if(p >= gameInfo.holdingsSize) p = 0;
7583              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7584                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7585              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7586                         board[BOARD_HEIGHT-1-p][1] = 0;
7587         }
7588       }
7589       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7590           && gameInfo.variant != VariantBughouse        ) {
7591         /* [HGM] holdings: Add to holdings, if holdings exist */
7592         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7593                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7594                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7595         }
7596         p = (int) captured;
7597         if (p >= (int) BlackPawn) {
7598           p -= (int)BlackPawn;
7599           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7600                   /* in Shogi restore piece to its original  first */
7601                   captured = (ChessSquare) (DEMOTED captured);
7602                   p = DEMOTED p;
7603           }
7604           p = PieceToNumber((ChessSquare)p);
7605           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7606           board[p][BOARD_WIDTH-2]++;
7607           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7608         } else {
7609           p -= (int)WhitePawn;
7610           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7611                   captured = (ChessSquare) (DEMOTED captured);
7612                   p = DEMOTED p;
7613           }
7614           p = PieceToNumber((ChessSquare)p);
7615           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7616           board[BOARD_HEIGHT-1-p][1]++;
7617           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7618         }
7619       }
7620     } else if (gameInfo.variant == VariantAtomic) {
7621       if (captured != EmptySquare) {
7622         int y, x;
7623         for (y = toY-1; y <= toY+1; y++) {
7624           for (x = toX-1; x <= toX+1; x++) {
7625             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7626                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7627               board[y][x] = EmptySquare;
7628             }
7629           }
7630         }
7631         board[toY][toX] = EmptySquare;
7632       }
7633     }
7634     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7635         /* [HGM] Shogi promotions */
7636         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7637     }
7638
7639     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7640                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7641         // [HGM] superchess: take promotion piece out of holdings
7642         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7643         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7644             if(!--board[k][BOARD_WIDTH-2])
7645                 board[k][BOARD_WIDTH-1] = EmptySquare;
7646         } else {
7647             if(!--board[BOARD_HEIGHT-1-k][1])
7648                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7649         }
7650     }
7651
7652 }
7653
7654 /* Updates forwardMostMove */
7655 void
7656 MakeMove(fromX, fromY, toX, toY, promoChar)
7657      int fromX, fromY, toX, toY;
7658      int promoChar;
7659 {
7660 //    forwardMostMove++; // [HGM] bare: moved downstream
7661
7662     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7663         int timeLeft; static int lastLoadFlag=0; int king, piece;
7664         piece = boards[forwardMostMove][fromY][fromX];
7665         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7666         if(gameInfo.variant == VariantKnightmate)
7667             king += (int) WhiteUnicorn - (int) WhiteKing;
7668         if(forwardMostMove == 0) {
7669             if(blackPlaysFirst) 
7670                 fprintf(serverMoves, "%s;", second.tidy);
7671             fprintf(serverMoves, "%s;", first.tidy);
7672             if(!blackPlaysFirst) 
7673                 fprintf(serverMoves, "%s;", second.tidy);
7674         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7675         lastLoadFlag = loadFlag;
7676         // print base move
7677         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7678         // print castling suffix
7679         if( toY == fromY && piece == king ) {
7680             if(toX-fromX > 1)
7681                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7682             if(fromX-toX >1)
7683                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7684         }
7685         // e.p. suffix
7686         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7687              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7688              boards[forwardMostMove][toY][toX] == EmptySquare
7689              && fromX != toX )
7690                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7691         // promotion suffix
7692         if(promoChar != NULLCHAR)
7693                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7694         if(!loadFlag) {
7695             fprintf(serverMoves, "/%d/%d",
7696                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7697             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7698             else                      timeLeft = blackTimeRemaining/1000;
7699             fprintf(serverMoves, "/%d", timeLeft);
7700         }
7701         fflush(serverMoves);
7702     }
7703
7704     if (forwardMostMove+1 >= MAX_MOVES) {
7705       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7706                         0, 1);
7707       return;
7708     }
7709     if (commentList[forwardMostMove+1] != NULL) {
7710         free(commentList[forwardMostMove+1]);
7711         commentList[forwardMostMove+1] = NULL;
7712     }
7713     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7714     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7715     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7716                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7717     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7718     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7719     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7720     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7721     gameInfo.result = GameUnfinished;
7722     if (gameInfo.resultDetails != NULL) {
7723         free(gameInfo.resultDetails);
7724         gameInfo.resultDetails = NULL;
7725     }
7726     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7727                               moveList[forwardMostMove - 1]);
7728     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7729                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7730                              fromY, fromX, toY, toX, promoChar,
7731                              parseList[forwardMostMove - 1]);
7732     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7733                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7734                             castlingRights[forwardMostMove]) ) {
7735       case MT_NONE:
7736       case MT_STALEMATE:
7737       default:
7738         break;
7739       case MT_CHECK:
7740         if(gameInfo.variant != VariantShogi)
7741             strcat(parseList[forwardMostMove - 1], "+");
7742         break;
7743       case MT_CHECKMATE:
7744       case MT_STAINMATE:
7745         strcat(parseList[forwardMostMove - 1], "#");
7746         break;
7747     }
7748     if (appData.debugMode) {
7749         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7750     }
7751
7752 }
7753
7754 /* Updates currentMove if not pausing */
7755 void
7756 ShowMove(fromX, fromY, toX, toY)
7757 {
7758     int instant = (gameMode == PlayFromGameFile) ?
7759         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7760     if(appData.noGUI) return;
7761     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7762         if (!instant) {
7763             if (forwardMostMove == currentMove + 1) {
7764                 AnimateMove(boards[forwardMostMove - 1],
7765                             fromX, fromY, toX, toY);
7766             }
7767             if (appData.highlightLastMove) {
7768                 SetHighlights(fromX, fromY, toX, toY);
7769             }
7770         }
7771         currentMove = forwardMostMove;
7772     }
7773
7774     if (instant) return;
7775
7776     DisplayMove(currentMove - 1);
7777     DrawPosition(FALSE, boards[currentMove]);
7778     DisplayBothClocks();
7779     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7780 }
7781
7782 void SendEgtPath(ChessProgramState *cps)
7783 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7784         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7785
7786         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7787
7788         while(*p) {
7789             char c, *q = name+1, *r, *s;
7790
7791             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7792             while(*p && *p != ',') *q++ = *p++;
7793             *q++ = ':'; *q = 0;
7794             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7795                 strcmp(name, ",nalimov:") == 0 ) {
7796                 // take nalimov path from the menu-changeable option first, if it is defined
7797                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7798                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7799             } else
7800             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7801                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7802                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7803                 s = r = StrStr(s, ":") + 1; // beginning of path info
7804                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7805                 c = *r; *r = 0;             // temporarily null-terminate path info
7806                     *--q = 0;               // strip of trailig ':' from name
7807                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7808                 *r = c;
7809                 SendToProgram(buf,cps);     // send egtbpath command for this format
7810             }
7811             if(*p == ',') p++; // read away comma to position for next format name
7812         }
7813 }
7814
7815 void
7816 InitChessProgram(cps, setup)
7817      ChessProgramState *cps;
7818      int setup; /* [HGM] needed to setup FRC opening position */
7819 {
7820     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7821     if (appData.noChessProgram) return;
7822     hintRequested = FALSE;
7823     bookRequested = FALSE;
7824
7825     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7826     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7827     if(cps->memSize) { /* [HGM] memory */
7828         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7829         SendToProgram(buf, cps);
7830     }
7831     SendEgtPath(cps); /* [HGM] EGT */
7832     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7833         sprintf(buf, "cores %d\n", appData.smpCores);
7834         SendToProgram(buf, cps);
7835     }
7836
7837     SendToProgram(cps->initString, cps);
7838     if (gameInfo.variant != VariantNormal &&
7839         gameInfo.variant != VariantLoadable
7840         /* [HGM] also send variant if board size non-standard */
7841         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7842                                             ) {
7843       char *v = VariantName(gameInfo.variant);
7844       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7845         /* [HGM] in protocol 1 we have to assume all variants valid */
7846         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7847         DisplayFatalError(buf, 0, 1);
7848         return;
7849       }
7850
7851       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7852       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7853       if( gameInfo.variant == VariantXiangqi )
7854            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7855       if( gameInfo.variant == VariantShogi )
7856            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7857       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7858            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7859       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7860                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7861            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7862       if( gameInfo.variant == VariantCourier )
7863            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7864       if( gameInfo.variant == VariantSuper )
7865            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7866       if( gameInfo.variant == VariantGreat )
7867            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7868
7869       if(overruled) {
7870            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7871                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7872            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7873            if(StrStr(cps->variants, b) == NULL) { 
7874                // specific sized variant not known, check if general sizing allowed
7875                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7876                    if(StrStr(cps->variants, "boardsize") == NULL) {
7877                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7878                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7879                        DisplayFatalError(buf, 0, 1);
7880                        return;
7881                    }
7882                    /* [HGM] here we really should compare with the maximum supported board size */
7883                }
7884            }
7885       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7886       sprintf(buf, "variant %s\n", b);
7887       SendToProgram(buf, cps);
7888     }
7889     currentlyInitializedVariant = gameInfo.variant;
7890
7891     /* [HGM] send opening position in FRC to first engine */
7892     if(setup) {
7893           SendToProgram("force\n", cps);
7894           SendBoard(cps, 0);
7895           /* engine is now in force mode! Set flag to wake it up after first move. */
7896           setboardSpoiledMachineBlack = 1;
7897     }
7898
7899     if (cps->sendICS) {
7900       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7901       SendToProgram(buf, cps);
7902     }
7903     cps->maybeThinking = FALSE;
7904     cps->offeredDraw = 0;
7905     if (!appData.icsActive) {
7906         SendTimeControl(cps, movesPerSession, timeControl,
7907                         timeIncrement, appData.searchDepth,
7908                         searchTime);
7909     }
7910     if (appData.showThinking 
7911         // [HGM] thinking: four options require thinking output to be sent
7912         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7913                                 ) {
7914         SendToProgram("post\n", cps);
7915     }
7916     SendToProgram("hard\n", cps);
7917     if (!appData.ponderNextMove) {
7918         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7919            it without being sure what state we are in first.  "hard"
7920            is not a toggle, so that one is OK.
7921          */
7922         SendToProgram("easy\n", cps);
7923     }
7924     if (cps->usePing) {
7925       sprintf(buf, "ping %d\n", ++cps->lastPing);
7926       SendToProgram(buf, cps);
7927     }
7928     cps->initDone = TRUE;
7929 }   
7930
7931
7932 void
7933 StartChessProgram(cps)
7934      ChessProgramState *cps;
7935 {
7936     char buf[MSG_SIZ];
7937     int err;
7938
7939     if (appData.noChessProgram) return;
7940     cps->initDone = FALSE;
7941
7942     if (strcmp(cps->host, "localhost") == 0) {
7943         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7944     } else if (*appData.remoteShell == NULLCHAR) {
7945         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7946     } else {
7947         if (*appData.remoteUser == NULLCHAR) {
7948           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7949                     cps->program);
7950         } else {
7951           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7952                     cps->host, appData.remoteUser, cps->program);
7953         }
7954         err = StartChildProcess(buf, "", &cps->pr);
7955     }
7956     
7957     if (err != 0) {
7958         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7959         DisplayFatalError(buf, err, 1);
7960         cps->pr = NoProc;
7961         cps->isr = NULL;
7962         return;
7963     }
7964     
7965     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7966     if (cps->protocolVersion > 1) {
7967       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7968       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7969       cps->comboCnt = 0;  //                and values of combo boxes
7970       SendToProgram(buf, cps);
7971     } else {
7972       SendToProgram("xboard\n", cps);
7973     }
7974 }
7975
7976
7977 void
7978 TwoMachinesEventIfReady P((void))
7979 {
7980   if (first.lastPing != first.lastPong) {
7981     DisplayMessage("", _("Waiting for first chess program"));
7982     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7983     return;
7984   }
7985   if (second.lastPing != second.lastPong) {
7986     DisplayMessage("", _("Waiting for second chess program"));
7987     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7988     return;
7989   }
7990   ThawUI();
7991   TwoMachinesEvent();
7992 }
7993
7994 void
7995 NextMatchGame P((void))
7996 {
7997     int index; /* [HGM] autoinc: step load index during match */
7998     Reset(FALSE, TRUE);
7999     if (*appData.loadGameFile != NULLCHAR) {
8000         index = appData.loadGameIndex;
8001         if(index < 0) { // [HGM] autoinc
8002             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8003             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8004         } 
8005         LoadGameFromFile(appData.loadGameFile,
8006                          index,
8007                          appData.loadGameFile, FALSE);
8008     } else if (*appData.loadPositionFile != NULLCHAR) {
8009         index = appData.loadPositionIndex;
8010         if(index < 0) { // [HGM] autoinc
8011             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8012             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8013         } 
8014         LoadPositionFromFile(appData.loadPositionFile,
8015                              index,
8016                              appData.loadPositionFile);
8017     }
8018     TwoMachinesEventIfReady();
8019 }
8020
8021 void UserAdjudicationEvent( int result )
8022 {
8023     ChessMove gameResult = GameIsDrawn;
8024
8025     if( result > 0 ) {
8026         gameResult = WhiteWins;
8027     }
8028     else if( result < 0 ) {
8029         gameResult = BlackWins;
8030     }
8031
8032     if( gameMode == TwoMachinesPlay ) {
8033         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8034     }
8035 }
8036
8037
8038 // [HGM] save: calculate checksum of game to make games easily identifiable
8039 int StringCheckSum(char *s)
8040 {
8041         int i = 0;
8042         if(s==NULL) return 0;
8043         while(*s) i = i*259 + *s++;
8044         return i;
8045 }
8046
8047 int GameCheckSum()
8048 {
8049         int i, sum=0;
8050         for(i=backwardMostMove; i<forwardMostMove; i++) {
8051                 sum += pvInfoList[i].depth;
8052                 sum += StringCheckSum(parseList[i]);
8053                 sum += StringCheckSum(commentList[i]);
8054                 sum *= 261;
8055         }
8056         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8057         return sum + StringCheckSum(commentList[i]);
8058 } // end of save patch
8059
8060 void
8061 GameEnds(result, resultDetails, whosays)
8062      ChessMove result;
8063      char *resultDetails;
8064      int whosays;
8065 {
8066     GameMode nextGameMode;
8067     int isIcsGame;
8068     char buf[MSG_SIZ];
8069
8070     if(endingGame) return; /* [HGM] crash: forbid recursion */
8071     endingGame = 1;
8072
8073     if (appData.debugMode) {
8074       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8075               result, resultDetails ? resultDetails : "(null)", whosays);
8076     }
8077
8078     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8079         /* If we are playing on ICS, the server decides when the
8080            game is over, but the engine can offer to draw, claim 
8081            a draw, or resign. 
8082          */
8083 #if ZIPPY
8084         if (appData.zippyPlay && first.initDone) {
8085             if (result == GameIsDrawn) {
8086                 /* In case draw still needs to be claimed */
8087                 SendToICS(ics_prefix);
8088                 SendToICS("draw\n");
8089             } else if (StrCaseStr(resultDetails, "resign")) {
8090                 SendToICS(ics_prefix);
8091                 SendToICS("resign\n");
8092             }
8093         }
8094 #endif
8095         endingGame = 0; /* [HGM] crash */
8096         return;
8097     }
8098
8099     /* If we're loading the game from a file, stop */
8100     if (whosays == GE_FILE) {
8101       (void) StopLoadGameTimer();
8102       gameFileFP = NULL;
8103     }
8104
8105     /* Cancel draw offers */
8106     first.offeredDraw = second.offeredDraw = 0;
8107
8108     /* If this is an ICS game, only ICS can really say it's done;
8109        if not, anyone can. */
8110     isIcsGame = (gameMode == IcsPlayingWhite || 
8111                  gameMode == IcsPlayingBlack || 
8112                  gameMode == IcsObserving    || 
8113                  gameMode == IcsExamining);
8114
8115     if (!isIcsGame || whosays == GE_ICS) {
8116         /* OK -- not an ICS game, or ICS said it was done */
8117         StopClocks();
8118         if (!isIcsGame && !appData.noChessProgram) 
8119           SetUserThinkingEnables();
8120     
8121         /* [HGM] if a machine claims the game end we verify this claim */
8122         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8123             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8124                 char claimer;
8125                 ChessMove trueResult = (ChessMove) -1;
8126
8127                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8128                                             first.twoMachinesColor[0] :
8129                                             second.twoMachinesColor[0] ;
8130
8131                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8132                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8133                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8134                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8135                 } else
8136                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8137                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8138                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8139                 } else
8140                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8141                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8142                 }
8143
8144                 // now verify win claims, but not in drop games, as we don't understand those yet
8145                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8146                                                  || gameInfo.variant == VariantGreat) &&
8147                     (result == WhiteWins && claimer == 'w' ||
8148                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8149                       if (appData.debugMode) {
8150                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8151                                 result, epStatus[forwardMostMove], forwardMostMove);
8152                       }
8153                       if(result != trueResult) {
8154                               sprintf(buf, "False win claim: '%s'", resultDetails);
8155                               result = claimer == 'w' ? BlackWins : WhiteWins;
8156                               resultDetails = buf;
8157                       }
8158                 } else
8159                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8160                     && (forwardMostMove <= backwardMostMove ||
8161                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8162                         (claimer=='b')==(forwardMostMove&1))
8163                                                                                   ) {
8164                       /* [HGM] verify: draws that were not flagged are false claims */
8165                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8166                       result = claimer == 'w' ? BlackWins : WhiteWins;
8167                       resultDetails = buf;
8168                 }
8169                 /* (Claiming a loss is accepted no questions asked!) */
8170             }
8171             /* [HGM] bare: don't allow bare King to win */
8172             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8173                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8174                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8175                && result != GameIsDrawn)
8176             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8177                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8178                         int p = (int)boards[forwardMostMove][i][j] - color;
8179                         if(p >= 0 && p <= (int)WhiteKing) k++;
8180                 }
8181                 if (appData.debugMode) {
8182                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8183                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8184                 }
8185                 if(k <= 1) {
8186                         result = GameIsDrawn;
8187                         sprintf(buf, "%s but bare king", resultDetails);
8188                         resultDetails = buf;
8189                 }
8190             }
8191         }
8192
8193
8194         if(serverMoves != NULL && !loadFlag) { char c = '=';
8195             if(result==WhiteWins) c = '+';
8196             if(result==BlackWins) c = '-';
8197             if(resultDetails != NULL)
8198                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8199         }
8200         if (resultDetails != NULL) {
8201             gameInfo.result = result;
8202             gameInfo.resultDetails = StrSave(resultDetails);
8203
8204             /* display last move only if game was not loaded from file */
8205             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8206                 DisplayMove(currentMove - 1);
8207     
8208             if (forwardMostMove != 0) {
8209                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8210                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8211                                                                 ) {
8212                     if (*appData.saveGameFile != NULLCHAR) {
8213                         SaveGameToFile(appData.saveGameFile, TRUE);
8214                     } else if (appData.autoSaveGames) {
8215                         AutoSaveGame();
8216                     }
8217                     if (*appData.savePositionFile != NULLCHAR) {
8218                         SavePositionToFile(appData.savePositionFile);
8219                     }
8220                 }
8221             }
8222
8223             /* Tell program how game ended in case it is learning */
8224             /* [HGM] Moved this to after saving the PGN, just in case */
8225             /* engine died and we got here through time loss. In that */
8226             /* case we will get a fatal error writing the pipe, which */
8227             /* would otherwise lose us the PGN.                       */
8228             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8229             /* output during GameEnds should never be fatal anymore   */
8230             if (gameMode == MachinePlaysWhite ||
8231                 gameMode == MachinePlaysBlack ||
8232                 gameMode == TwoMachinesPlay ||
8233                 gameMode == IcsPlayingWhite ||
8234                 gameMode == IcsPlayingBlack ||
8235                 gameMode == BeginningOfGame) {
8236                 char buf[MSG_SIZ];
8237                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8238                         resultDetails);
8239                 if (first.pr != NoProc) {
8240                     SendToProgram(buf, &first);
8241                 }
8242                 if (second.pr != NoProc &&
8243                     gameMode == TwoMachinesPlay) {
8244                     SendToProgram(buf, &second);
8245                 }
8246             }
8247         }
8248
8249         if (appData.icsActive) {
8250             if (appData.quietPlay &&
8251                 (gameMode == IcsPlayingWhite ||
8252                  gameMode == IcsPlayingBlack)) {
8253                 SendToICS(ics_prefix);
8254                 SendToICS("set shout 1\n");
8255             }
8256             nextGameMode = IcsIdle;
8257             ics_user_moved = FALSE;
8258             /* clean up premove.  It's ugly when the game has ended and the
8259              * premove highlights are still on the board.
8260              */
8261             if (gotPremove) {
8262               gotPremove = FALSE;
8263               ClearPremoveHighlights();
8264               DrawPosition(FALSE, boards[currentMove]);
8265             }
8266             if (whosays == GE_ICS) {
8267                 switch (result) {
8268                 case WhiteWins:
8269                     if (gameMode == IcsPlayingWhite)
8270                         PlayIcsWinSound();
8271                     else if(gameMode == IcsPlayingBlack)
8272                         PlayIcsLossSound();
8273                     break;
8274                 case BlackWins:
8275                     if (gameMode == IcsPlayingBlack)
8276                         PlayIcsWinSound();
8277                     else if(gameMode == IcsPlayingWhite)
8278                         PlayIcsLossSound();
8279                     break;
8280                 case GameIsDrawn:
8281                     PlayIcsDrawSound();
8282                     break;
8283                 default:
8284                     PlayIcsUnfinishedSound();
8285                 }
8286             }
8287         } else if (gameMode == EditGame ||
8288                    gameMode == PlayFromGameFile || 
8289                    gameMode == AnalyzeMode || 
8290                    gameMode == AnalyzeFile) {
8291             nextGameMode = gameMode;
8292         } else {
8293             nextGameMode = EndOfGame;
8294         }
8295         pausing = FALSE;
8296         ModeHighlight();
8297     } else {
8298         nextGameMode = gameMode;
8299     }
8300
8301     if (appData.noChessProgram) {
8302         gameMode = nextGameMode;
8303         ModeHighlight();
8304         endingGame = 0; /* [HGM] crash */
8305         return;
8306     }
8307
8308     if (first.reuse) {
8309         /* Put first chess program into idle state */
8310         if (first.pr != NoProc &&
8311             (gameMode == MachinePlaysWhite ||
8312              gameMode == MachinePlaysBlack ||
8313              gameMode == TwoMachinesPlay ||
8314              gameMode == IcsPlayingWhite ||
8315              gameMode == IcsPlayingBlack ||
8316              gameMode == BeginningOfGame)) {
8317             SendToProgram("force\n", &first);
8318             if (first.usePing) {
8319               char buf[MSG_SIZ];
8320               sprintf(buf, "ping %d\n", ++first.lastPing);
8321               SendToProgram(buf, &first);
8322             }
8323         }
8324     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8325         /* Kill off first chess program */
8326         if (first.isr != NULL)
8327           RemoveInputSource(first.isr);
8328         first.isr = NULL;
8329     
8330         if (first.pr != NoProc) {
8331             ExitAnalyzeMode();
8332             DoSleep( appData.delayBeforeQuit );
8333             SendToProgram("quit\n", &first);
8334             DoSleep( appData.delayAfterQuit );
8335             DestroyChildProcess(first.pr, first.useSigterm);
8336         }
8337         first.pr = NoProc;
8338     }
8339     if (second.reuse) {
8340         /* Put second chess program into idle state */
8341         if (second.pr != NoProc &&
8342             gameMode == TwoMachinesPlay) {
8343             SendToProgram("force\n", &second);
8344             if (second.usePing) {
8345               char buf[MSG_SIZ];
8346               sprintf(buf, "ping %d\n", ++second.lastPing);
8347               SendToProgram(buf, &second);
8348             }
8349         }
8350     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8351         /* Kill off second chess program */
8352         if (second.isr != NULL)
8353           RemoveInputSource(second.isr);
8354         second.isr = NULL;
8355     
8356         if (second.pr != NoProc) {
8357             DoSleep( appData.delayBeforeQuit );
8358             SendToProgram("quit\n", &second);
8359             DoSleep( appData.delayAfterQuit );
8360             DestroyChildProcess(second.pr, second.useSigterm);
8361         }
8362         second.pr = NoProc;
8363     }
8364
8365     if (matchMode && gameMode == TwoMachinesPlay) {
8366         switch (result) {
8367         case WhiteWins:
8368           if (first.twoMachinesColor[0] == 'w') {
8369             first.matchWins++;
8370           } else {
8371             second.matchWins++;
8372           }
8373           break;
8374         case BlackWins:
8375           if (first.twoMachinesColor[0] == 'b') {
8376             first.matchWins++;
8377           } else {
8378             second.matchWins++;
8379           }
8380           break;
8381         default:
8382           break;
8383         }
8384         if (matchGame < appData.matchGames) {
8385             char *tmp;
8386             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8387                 tmp = first.twoMachinesColor;
8388                 first.twoMachinesColor = second.twoMachinesColor;
8389                 second.twoMachinesColor = tmp;
8390             }
8391             gameMode = nextGameMode;
8392             matchGame++;
8393             if(appData.matchPause>10000 || appData.matchPause<10)
8394                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8395             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8396             endingGame = 0; /* [HGM] crash */
8397             return;
8398         } else {
8399             char buf[MSG_SIZ];
8400             gameMode = nextGameMode;
8401             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8402                     first.tidy, second.tidy,
8403                     first.matchWins, second.matchWins,
8404                     appData.matchGames - (first.matchWins + second.matchWins));
8405             DisplayFatalError(buf, 0, 0);
8406         }
8407     }
8408     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8409         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8410       ExitAnalyzeMode();
8411     gameMode = nextGameMode;
8412     ModeHighlight();
8413     endingGame = 0;  /* [HGM] crash */
8414 }
8415
8416 /* Assumes program was just initialized (initString sent).
8417    Leaves program in force mode. */
8418 void
8419 FeedMovesToProgram(cps, upto) 
8420      ChessProgramState *cps;
8421      int upto;
8422 {
8423     int i;
8424     
8425     if (appData.debugMode)
8426       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8427               startedFromSetupPosition ? "position and " : "",
8428               backwardMostMove, upto, cps->which);
8429     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8430         // [HGM] variantswitch: make engine aware of new variant
8431         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8432                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8433         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8434         SendToProgram(buf, cps);
8435         currentlyInitializedVariant = gameInfo.variant;
8436     }
8437     SendToProgram("force\n", cps);
8438     if (startedFromSetupPosition) {
8439         SendBoard(cps, backwardMostMove);
8440     if (appData.debugMode) {
8441         fprintf(debugFP, "feedMoves\n");
8442     }
8443     }
8444     for (i = backwardMostMove; i < upto; i++) {
8445         SendMoveToProgram(i, cps);
8446     }
8447 }
8448
8449
8450 void
8451 ResurrectChessProgram()
8452 {
8453      /* The chess program may have exited.
8454         If so, restart it and feed it all the moves made so far. */
8455
8456     if (appData.noChessProgram || first.pr != NoProc) return;
8457     
8458     StartChessProgram(&first);
8459     InitChessProgram(&first, FALSE);
8460     FeedMovesToProgram(&first, currentMove);
8461
8462     if (!first.sendTime) {
8463         /* can't tell gnuchess what its clock should read,
8464            so we bow to its notion. */
8465         ResetClocks();
8466         timeRemaining[0][currentMove] = whiteTimeRemaining;
8467         timeRemaining[1][currentMove] = blackTimeRemaining;
8468     }
8469
8470     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8471                 appData.icsEngineAnalyze) && first.analysisSupport) {
8472       SendToProgram("analyze\n", &first);
8473       first.analyzing = TRUE;
8474     }
8475 }
8476
8477 /*
8478  * Button procedures
8479  */
8480 void
8481 Reset(redraw, init)
8482      int redraw, init;
8483 {
8484     int i;
8485
8486     if (appData.debugMode) {
8487         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8488                 redraw, init, gameMode);
8489     }
8490     pausing = pauseExamInvalid = FALSE;
8491     startedFromSetupPosition = blackPlaysFirst = FALSE;
8492     firstMove = TRUE;
8493     whiteFlag = blackFlag = FALSE;
8494     userOfferedDraw = FALSE;
8495     hintRequested = bookRequested = FALSE;
8496     first.maybeThinking = FALSE;
8497     second.maybeThinking = FALSE;
8498     first.bookSuspend = FALSE; // [HGM] book
8499     second.bookSuspend = FALSE;
8500     thinkOutput[0] = NULLCHAR;
8501     lastHint[0] = NULLCHAR;
8502     ClearGameInfo(&gameInfo);
8503     gameInfo.variant = StringToVariant(appData.variant);
8504     ics_user_moved = ics_clock_paused = FALSE;
8505     ics_getting_history = H_FALSE;
8506     ics_gamenum = -1;
8507     white_holding[0] = black_holding[0] = NULLCHAR;
8508     ClearProgramStats();
8509     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8510     
8511     ResetFrontEnd();
8512     ClearHighlights();
8513     flipView = appData.flipView;
8514     ClearPremoveHighlights();
8515     gotPremove = FALSE;
8516     alarmSounded = FALSE;
8517
8518     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8519     if(appData.serverMovesName != NULL) {
8520         /* [HGM] prepare to make moves file for broadcasting */
8521         clock_t t = clock();
8522         if(serverMoves != NULL) fclose(serverMoves);
8523         serverMoves = fopen(appData.serverMovesName, "r");
8524         if(serverMoves != NULL) {
8525             fclose(serverMoves);
8526             /* delay 15 sec before overwriting, so all clients can see end */
8527             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8528         }
8529         serverMoves = fopen(appData.serverMovesName, "w");
8530     }
8531
8532     ExitAnalyzeMode();
8533     gameMode = BeginningOfGame;
8534     ModeHighlight();
8535     if(appData.icsActive) gameInfo.variant = VariantNormal;
8536     currentMove = forwardMostMove = backwardMostMove = 0;
8537     InitPosition(redraw);
8538     for (i = 0; i < MAX_MOVES; i++) {
8539         if (commentList[i] != NULL) {
8540             free(commentList[i]);
8541             commentList[i] = NULL;
8542         }
8543     }
8544     ResetClocks();
8545     timeRemaining[0][0] = whiteTimeRemaining;
8546     timeRemaining[1][0] = blackTimeRemaining;
8547     if (first.pr == NULL) {
8548         StartChessProgram(&first);
8549     }
8550     if (init) {
8551             InitChessProgram(&first, startedFromSetupPosition);
8552     }
8553     DisplayTitle("");
8554     DisplayMessage("", "");
8555     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8556     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8557 }
8558
8559 void
8560 AutoPlayGameLoop()
8561 {
8562     for (;;) {
8563         if (!AutoPlayOneMove())
8564           return;
8565         if (matchMode || appData.timeDelay == 0)
8566           continue;
8567         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8568           return;
8569         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8570         break;
8571     }
8572 }
8573
8574
8575 int
8576 AutoPlayOneMove()
8577 {
8578     int fromX, fromY, toX, toY;
8579
8580     if (appData.debugMode) {
8581       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8582     }
8583
8584     if (gameMode != PlayFromGameFile)
8585       return FALSE;
8586
8587     if (currentMove >= forwardMostMove) {
8588       gameMode = EditGame;
8589       ModeHighlight();
8590
8591       /* [AS] Clear current move marker at the end of a game */
8592       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8593
8594       return FALSE;
8595     }
8596     
8597     toX = moveList[currentMove][2] - AAA;
8598     toY = moveList[currentMove][3] - ONE;
8599
8600     if (moveList[currentMove][1] == '@') {
8601         if (appData.highlightLastMove) {
8602             SetHighlights(-1, -1, toX, toY);
8603         }
8604     } else {
8605         fromX = moveList[currentMove][0] - AAA;
8606         fromY = moveList[currentMove][1] - ONE;
8607
8608         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8609
8610         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8611
8612         if (appData.highlightLastMove) {
8613             SetHighlights(fromX, fromY, toX, toY);
8614         }
8615     }
8616     DisplayMove(currentMove);
8617     SendMoveToProgram(currentMove++, &first);
8618     DisplayBothClocks();
8619     DrawPosition(FALSE, boards[currentMove]);
8620     // [HGM] PV info: always display, routine tests if empty
8621     DisplayComment(currentMove - 1, commentList[currentMove]);
8622     return TRUE;
8623 }
8624
8625
8626 int
8627 LoadGameOneMove(readAhead)
8628      ChessMove readAhead;
8629 {
8630     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8631     char promoChar = NULLCHAR;
8632     ChessMove moveType;
8633     char move[MSG_SIZ];
8634     char *p, *q;
8635     
8636     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8637         gameMode != AnalyzeMode && gameMode != Training) {
8638         gameFileFP = NULL;
8639         return FALSE;
8640     }
8641     
8642     yyboardindex = forwardMostMove;
8643     if (readAhead != (ChessMove)0) {
8644       moveType = readAhead;
8645     } else {
8646       if (gameFileFP == NULL)
8647           return FALSE;
8648       moveType = (ChessMove) yylex();
8649     }
8650     
8651     done = FALSE;
8652     switch (moveType) {
8653       case Comment:
8654         if (appData.debugMode) 
8655           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8656         p = yy_text;
8657         if (*p == '{' || *p == '[' || *p == '(') {
8658             p[strlen(p) - 1] = NULLCHAR;
8659             p++;
8660         }
8661
8662         /* append the comment but don't display it */
8663         while (*p == '\n') p++;
8664         AppendComment(currentMove, p);
8665         return TRUE;
8666
8667       case WhiteCapturesEnPassant:
8668       case BlackCapturesEnPassant:
8669       case WhitePromotionChancellor:
8670       case BlackPromotionChancellor:
8671       case WhitePromotionArchbishop:
8672       case BlackPromotionArchbishop:
8673       case WhitePromotionCentaur:
8674       case BlackPromotionCentaur:
8675       case WhitePromotionQueen:
8676       case BlackPromotionQueen:
8677       case WhitePromotionRook:
8678       case BlackPromotionRook:
8679       case WhitePromotionBishop:
8680       case BlackPromotionBishop:
8681       case WhitePromotionKnight:
8682       case BlackPromotionKnight:
8683       case WhitePromotionKing:
8684       case BlackPromotionKing:
8685       case NormalMove:
8686       case WhiteKingSideCastle:
8687       case WhiteQueenSideCastle:
8688       case BlackKingSideCastle:
8689       case BlackQueenSideCastle:
8690       case WhiteKingSideCastleWild:
8691       case WhiteQueenSideCastleWild:
8692       case BlackKingSideCastleWild:
8693       case BlackQueenSideCastleWild:
8694       /* PUSH Fabien */
8695       case WhiteHSideCastleFR:
8696       case WhiteASideCastleFR:
8697       case BlackHSideCastleFR:
8698       case BlackASideCastleFR:
8699       /* POP Fabien */
8700         if (appData.debugMode)
8701           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8702         fromX = currentMoveString[0] - AAA;
8703         fromY = currentMoveString[1] - ONE;
8704         toX = currentMoveString[2] - AAA;
8705         toY = currentMoveString[3] - ONE;
8706         promoChar = currentMoveString[4];
8707         break;
8708
8709       case WhiteDrop:
8710       case BlackDrop:
8711         if (appData.debugMode)
8712           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8713         fromX = moveType == WhiteDrop ?
8714           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8715         (int) CharToPiece(ToLower(currentMoveString[0]));
8716         fromY = DROP_RANK;
8717         toX = currentMoveString[2] - AAA;
8718         toY = currentMoveString[3] - ONE;
8719         break;
8720
8721       case WhiteWins:
8722       case BlackWins:
8723       case GameIsDrawn:
8724       case GameUnfinished:
8725         if (appData.debugMode)
8726           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8727         p = strchr(yy_text, '{');
8728         if (p == NULL) p = strchr(yy_text, '(');
8729         if (p == NULL) {
8730             p = yy_text;
8731             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8732         } else {
8733             q = strchr(p, *p == '{' ? '}' : ')');
8734             if (q != NULL) *q = NULLCHAR;
8735             p++;
8736         }
8737         GameEnds(moveType, p, GE_FILE);
8738         done = TRUE;
8739         if (cmailMsgLoaded) {
8740             ClearHighlights();
8741             flipView = WhiteOnMove(currentMove);
8742             if (moveType == GameUnfinished) flipView = !flipView;
8743             if (appData.debugMode)
8744               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8745         }
8746         break;
8747
8748       case (ChessMove) 0:       /* end of file */
8749         if (appData.debugMode)
8750           fprintf(debugFP, "Parser hit end of file\n");
8751         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8752                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8753           case MT_NONE:
8754           case MT_CHECK:
8755             break;
8756           case MT_CHECKMATE:
8757           case MT_STAINMATE:
8758             if (WhiteOnMove(currentMove)) {
8759                 GameEnds(BlackWins, "Black mates", GE_FILE);
8760             } else {
8761                 GameEnds(WhiteWins, "White mates", GE_FILE);
8762             }
8763             break;
8764           case MT_STALEMATE:
8765             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8766             break;
8767         }
8768         done = TRUE;
8769         break;
8770
8771       case MoveNumberOne:
8772         if (lastLoadGameStart == GNUChessGame) {
8773             /* GNUChessGames have numbers, but they aren't move numbers */
8774             if (appData.debugMode)
8775               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8776                       yy_text, (int) moveType);
8777             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8778         }
8779         /* else fall thru */
8780
8781       case XBoardGame:
8782       case GNUChessGame:
8783       case PGNTag:
8784         /* Reached start of next game in file */
8785         if (appData.debugMode)
8786           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8787         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8788                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8789           case MT_NONE:
8790           case MT_CHECK:
8791             break;
8792           case MT_CHECKMATE:
8793           case MT_STAINMATE:
8794             if (WhiteOnMove(currentMove)) {
8795                 GameEnds(BlackWins, "Black mates", GE_FILE);
8796             } else {
8797                 GameEnds(WhiteWins, "White mates", GE_FILE);
8798             }
8799             break;
8800           case MT_STALEMATE:
8801             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8802             break;
8803         }
8804         done = TRUE;
8805         break;
8806
8807       case PositionDiagram:     /* should not happen; ignore */
8808       case ElapsedTime:         /* ignore */
8809       case NAG:                 /* ignore */
8810         if (appData.debugMode)
8811           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8812                   yy_text, (int) moveType);
8813         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8814
8815       case IllegalMove:
8816         if (appData.testLegality) {
8817             if (appData.debugMode)
8818               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8819             sprintf(move, _("Illegal move: %d.%s%s"),
8820                     (forwardMostMove / 2) + 1,
8821                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8822             DisplayError(move, 0);
8823             done = TRUE;
8824         } else {
8825             if (appData.debugMode)
8826               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8827                       yy_text, currentMoveString);
8828             fromX = currentMoveString[0] - AAA;
8829             fromY = currentMoveString[1] - ONE;
8830             toX = currentMoveString[2] - AAA;
8831             toY = currentMoveString[3] - ONE;
8832             promoChar = currentMoveString[4];
8833         }
8834         break;
8835
8836       case AmbiguousMove:
8837         if (appData.debugMode)
8838           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8839         sprintf(move, _("Ambiguous move: %d.%s%s"),
8840                 (forwardMostMove / 2) + 1,
8841                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8842         DisplayError(move, 0);
8843         done = TRUE;
8844         break;
8845
8846       default:
8847       case ImpossibleMove:
8848         if (appData.debugMode)
8849           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8850         sprintf(move, _("Illegal move: %d.%s%s"),
8851                 (forwardMostMove / 2) + 1,
8852                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8853         DisplayError(move, 0);
8854         done = TRUE;
8855         break;
8856     }
8857
8858     if (done) {
8859         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8860             DrawPosition(FALSE, boards[currentMove]);
8861             DisplayBothClocks();
8862             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8863               DisplayComment(currentMove - 1, commentList[currentMove]);
8864         }
8865         (void) StopLoadGameTimer();
8866         gameFileFP = NULL;
8867         cmailOldMove = forwardMostMove;
8868         return FALSE;
8869     } else {
8870         /* currentMoveString is set as a side-effect of yylex */
8871         strcat(currentMoveString, "\n");
8872         strcpy(moveList[forwardMostMove], currentMoveString);
8873         
8874         thinkOutput[0] = NULLCHAR;
8875         MakeMove(fromX, fromY, toX, toY, promoChar);
8876         currentMove = forwardMostMove;
8877         return TRUE;
8878     }
8879 }
8880
8881 /* Load the nth game from the given file */
8882 int
8883 LoadGameFromFile(filename, n, title, useList)
8884      char *filename;
8885      int n;
8886      char *title;
8887      /*Boolean*/ int useList;
8888 {
8889     FILE *f;
8890     char buf[MSG_SIZ];
8891
8892     if (strcmp(filename, "-") == 0) {
8893         f = stdin;
8894         title = "stdin";
8895     } else {
8896         f = fopen(filename, "rb");
8897         if (f == NULL) {
8898           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8899             DisplayError(buf, errno);
8900             return FALSE;
8901         }
8902     }
8903     if (fseek(f, 0, 0) == -1) {
8904         /* f is not seekable; probably a pipe */
8905         useList = FALSE;
8906     }
8907     if (useList && n == 0) {
8908         int error = GameListBuild(f);
8909         if (error) {
8910             DisplayError(_("Cannot build game list"), error);
8911         } else if (!ListEmpty(&gameList) &&
8912                    ((ListGame *) gameList.tailPred)->number > 1) {
8913             GameListPopUp(f, title);
8914             return TRUE;
8915         }
8916         GameListDestroy();
8917         n = 1;
8918     }
8919     if (n == 0) n = 1;
8920     return LoadGame(f, n, title, FALSE);
8921 }
8922
8923
8924 void
8925 MakeRegisteredMove()
8926 {
8927     int fromX, fromY, toX, toY;
8928     char promoChar;
8929     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8930         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8931           case CMAIL_MOVE:
8932           case CMAIL_DRAW:
8933             if (appData.debugMode)
8934               fprintf(debugFP, "Restoring %s for game %d\n",
8935                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8936     
8937             thinkOutput[0] = NULLCHAR;
8938             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8939             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8940             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8941             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8942             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8943             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8944             MakeMove(fromX, fromY, toX, toY, promoChar);
8945             ShowMove(fromX, fromY, toX, toY);
8946               
8947             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8948                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8949               case MT_NONE:
8950               case MT_CHECK:
8951                 break;
8952                 
8953               case MT_CHECKMATE:
8954               case MT_STAINMATE:
8955                 if (WhiteOnMove(currentMove)) {
8956                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8957                 } else {
8958                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8959                 }
8960                 break;
8961                 
8962               case MT_STALEMATE:
8963                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8964                 break;
8965             }
8966
8967             break;
8968             
8969           case CMAIL_RESIGN:
8970             if (WhiteOnMove(currentMove)) {
8971                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8972             } else {
8973                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8974             }
8975             break;
8976             
8977           case CMAIL_ACCEPT:
8978             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8979             break;
8980               
8981           default:
8982             break;
8983         }
8984     }
8985
8986     return;
8987 }
8988
8989 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8990 int
8991 CmailLoadGame(f, gameNumber, title, useList)
8992      FILE *f;
8993      int gameNumber;
8994      char *title;
8995      int useList;
8996 {
8997     int retVal;
8998
8999     if (gameNumber > nCmailGames) {
9000         DisplayError(_("No more games in this message"), 0);
9001         return FALSE;
9002     }
9003     if (f == lastLoadGameFP) {
9004         int offset = gameNumber - lastLoadGameNumber;
9005         if (offset == 0) {
9006             cmailMsg[0] = NULLCHAR;
9007             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9008                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9009                 nCmailMovesRegistered--;
9010             }
9011             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9012             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9013                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9014             }
9015         } else {
9016             if (! RegisterMove()) return FALSE;
9017         }
9018     }
9019
9020     retVal = LoadGame(f, gameNumber, title, useList);
9021
9022     /* Make move registered during previous look at this game, if any */
9023     MakeRegisteredMove();
9024
9025     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9026         commentList[currentMove]
9027           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9028         DisplayComment(currentMove - 1, commentList[currentMove]);
9029     }
9030
9031     return retVal;
9032 }
9033
9034 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9035 int
9036 ReloadGame(offset)
9037      int offset;
9038 {
9039     int gameNumber = lastLoadGameNumber + offset;
9040     if (lastLoadGameFP == NULL) {
9041         DisplayError(_("No game has been loaded yet"), 0);
9042         return FALSE;
9043     }
9044     if (gameNumber <= 0) {
9045         DisplayError(_("Can't back up any further"), 0);
9046         return FALSE;
9047     }
9048     if (cmailMsgLoaded) {
9049         return CmailLoadGame(lastLoadGameFP, gameNumber,
9050                              lastLoadGameTitle, lastLoadGameUseList);
9051     } else {
9052         return LoadGame(lastLoadGameFP, gameNumber,
9053                         lastLoadGameTitle, lastLoadGameUseList);
9054     }
9055 }
9056
9057
9058
9059 /* Load the nth game from open file f */
9060 int
9061 LoadGame(f, gameNumber, title, useList)
9062      FILE *f;
9063      int gameNumber;
9064      char *title;
9065      int useList;
9066 {
9067     ChessMove cm;
9068     char buf[MSG_SIZ];
9069     int gn = gameNumber;
9070     ListGame *lg = NULL;
9071     int numPGNTags = 0;
9072     int err;
9073     GameMode oldGameMode;
9074     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9075
9076     if (appData.debugMode) 
9077         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9078
9079     if (gameMode == Training )
9080         SetTrainingModeOff();
9081
9082     oldGameMode = gameMode;
9083     if (gameMode != BeginningOfGame) {
9084       Reset(FALSE, TRUE);
9085     }
9086
9087     gameFileFP = f;
9088     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9089         fclose(lastLoadGameFP);
9090     }
9091
9092     if (useList) {
9093         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9094         
9095         if (lg) {
9096             fseek(f, lg->offset, 0);
9097             GameListHighlight(gameNumber);
9098             gn = 1;
9099         }
9100         else {
9101             DisplayError(_("Game number out of range"), 0);
9102             return FALSE;
9103         }
9104     } else {
9105         GameListDestroy();
9106         if (fseek(f, 0, 0) == -1) {
9107             if (f == lastLoadGameFP ?
9108                 gameNumber == lastLoadGameNumber + 1 :
9109                 gameNumber == 1) {
9110                 gn = 1;
9111             } else {
9112                 DisplayError(_("Can't seek on game file"), 0);
9113                 return FALSE;
9114             }
9115         }
9116     }
9117     lastLoadGameFP = f;
9118     lastLoadGameNumber = gameNumber;
9119     strcpy(lastLoadGameTitle, title);
9120     lastLoadGameUseList = useList;
9121
9122     yynewfile(f);
9123
9124     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9125       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9126                 lg->gameInfo.black);
9127             DisplayTitle(buf);
9128     } else if (*title != NULLCHAR) {
9129         if (gameNumber > 1) {
9130             sprintf(buf, "%s %d", title, gameNumber);
9131             DisplayTitle(buf);
9132         } else {
9133             DisplayTitle(title);
9134         }
9135     }
9136
9137     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9138         gameMode = PlayFromGameFile;
9139         ModeHighlight();
9140     }
9141
9142     currentMove = forwardMostMove = backwardMostMove = 0;
9143     CopyBoard(boards[0], initialPosition);
9144     StopClocks();
9145
9146     /*
9147      * Skip the first gn-1 games in the file.
9148      * Also skip over anything that precedes an identifiable 
9149      * start of game marker, to avoid being confused by 
9150      * garbage at the start of the file.  Currently 
9151      * recognized start of game markers are the move number "1",
9152      * the pattern "gnuchess .* game", the pattern
9153      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9154      * A game that starts with one of the latter two patterns
9155      * will also have a move number 1, possibly
9156      * following a position diagram.
9157      * 5-4-02: Let's try being more lenient and allowing a game to
9158      * start with an unnumbered move.  Does that break anything?
9159      */
9160     cm = lastLoadGameStart = (ChessMove) 0;
9161     while (gn > 0) {
9162         yyboardindex = forwardMostMove;
9163         cm = (ChessMove) yylex();
9164         switch (cm) {
9165           case (ChessMove) 0:
9166             if (cmailMsgLoaded) {
9167                 nCmailGames = CMAIL_MAX_GAMES - gn;
9168             } else {
9169                 Reset(TRUE, TRUE);
9170                 DisplayError(_("Game not found in file"), 0);
9171             }
9172             return FALSE;
9173
9174           case GNUChessGame:
9175           case XBoardGame:
9176             gn--;
9177             lastLoadGameStart = cm;
9178             break;
9179             
9180           case MoveNumberOne:
9181             switch (lastLoadGameStart) {
9182               case GNUChessGame:
9183               case XBoardGame:
9184               case PGNTag:
9185                 break;
9186               case MoveNumberOne:
9187               case (ChessMove) 0:
9188                 gn--;           /* count this game */
9189                 lastLoadGameStart = cm;
9190                 break;
9191               default:
9192                 /* impossible */
9193                 break;
9194             }
9195             break;
9196
9197           case PGNTag:
9198             switch (lastLoadGameStart) {
9199               case GNUChessGame:
9200               case PGNTag:
9201               case MoveNumberOne:
9202               case (ChessMove) 0:
9203                 gn--;           /* count this game */
9204                 lastLoadGameStart = cm;
9205                 break;
9206               case XBoardGame:
9207                 lastLoadGameStart = cm; /* game counted already */
9208                 break;
9209               default:
9210                 /* impossible */
9211                 break;
9212             }
9213             if (gn > 0) {
9214                 do {
9215                     yyboardindex = forwardMostMove;
9216                     cm = (ChessMove) yylex();
9217                 } while (cm == PGNTag || cm == Comment);
9218             }
9219             break;
9220
9221           case WhiteWins:
9222           case BlackWins:
9223           case GameIsDrawn:
9224             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9225                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9226                     != CMAIL_OLD_RESULT) {
9227                     nCmailResults ++ ;
9228                     cmailResult[  CMAIL_MAX_GAMES
9229                                 - gn - 1] = CMAIL_OLD_RESULT;
9230                 }
9231             }
9232             break;
9233
9234           case NormalMove:
9235             /* Only a NormalMove can be at the start of a game
9236              * without a position diagram. */
9237             if (lastLoadGameStart == (ChessMove) 0) {
9238               gn--;
9239               lastLoadGameStart = MoveNumberOne;
9240             }
9241             break;
9242
9243           default:
9244             break;
9245         }
9246     }
9247     
9248     if (appData.debugMode)
9249       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9250
9251     if (cm == XBoardGame) {
9252         /* Skip any header junk before position diagram and/or move 1 */
9253         for (;;) {
9254             yyboardindex = forwardMostMove;
9255             cm = (ChessMove) yylex();
9256
9257             if (cm == (ChessMove) 0 ||
9258                 cm == GNUChessGame || cm == XBoardGame) {
9259                 /* Empty game; pretend end-of-file and handle later */
9260                 cm = (ChessMove) 0;
9261                 break;
9262             }
9263
9264             if (cm == MoveNumberOne || cm == PositionDiagram ||
9265                 cm == PGNTag || cm == Comment)
9266               break;
9267         }
9268     } else if (cm == GNUChessGame) {
9269         if (gameInfo.event != NULL) {
9270             free(gameInfo.event);
9271         }
9272         gameInfo.event = StrSave(yy_text);
9273     }   
9274
9275     startedFromSetupPosition = FALSE;
9276     while (cm == PGNTag) {
9277         if (appData.debugMode) 
9278           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9279         err = ParsePGNTag(yy_text, &gameInfo);
9280         if (!err) numPGNTags++;
9281
9282         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9283         if(gameInfo.variant != oldVariant) {
9284             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9285             InitPosition(TRUE);
9286             oldVariant = gameInfo.variant;
9287             if (appData.debugMode) 
9288               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9289         }
9290
9291
9292         if (gameInfo.fen != NULL) {
9293           Board initial_position;
9294           startedFromSetupPosition = TRUE;
9295           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9296             Reset(TRUE, TRUE);
9297             DisplayError(_("Bad FEN position in file"), 0);
9298             return FALSE;
9299           }
9300           CopyBoard(boards[0], initial_position);
9301           if (blackPlaysFirst) {
9302             currentMove = forwardMostMove = backwardMostMove = 1;
9303             CopyBoard(boards[1], initial_position);
9304             strcpy(moveList[0], "");
9305             strcpy(parseList[0], "");
9306             timeRemaining[0][1] = whiteTimeRemaining;
9307             timeRemaining[1][1] = blackTimeRemaining;
9308             if (commentList[0] != NULL) {
9309               commentList[1] = commentList[0];
9310               commentList[0] = NULL;
9311             }
9312           } else {
9313             currentMove = forwardMostMove = backwardMostMove = 0;
9314           }
9315           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9316           {   int i;
9317               initialRulePlies = FENrulePlies;
9318               epStatus[forwardMostMove] = FENepStatus;
9319               for( i=0; i< nrCastlingRights; i++ )
9320                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9321           }
9322           yyboardindex = forwardMostMove;
9323           free(gameInfo.fen);
9324           gameInfo.fen = NULL;
9325         }
9326
9327         yyboardindex = forwardMostMove;
9328         cm = (ChessMove) yylex();
9329
9330         /* Handle comments interspersed among the tags */
9331         while (cm == Comment) {
9332             char *p;
9333             if (appData.debugMode) 
9334               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9335             p = yy_text;
9336             if (*p == '{' || *p == '[' || *p == '(') {
9337                 p[strlen(p) - 1] = NULLCHAR;
9338                 p++;
9339             }
9340             while (*p == '\n') p++;
9341             AppendComment(currentMove, p);
9342             yyboardindex = forwardMostMove;
9343             cm = (ChessMove) yylex();
9344         }
9345     }
9346
9347     /* don't rely on existence of Event tag since if game was
9348      * pasted from clipboard the Event tag may not exist
9349      */
9350     if (numPGNTags > 0){
9351         char *tags;
9352         if (gameInfo.variant == VariantNormal) {
9353           gameInfo.variant = StringToVariant(gameInfo.event);
9354         }
9355         if (!matchMode) {
9356           if( appData.autoDisplayTags ) {
9357             tags = PGNTags(&gameInfo);
9358             TagsPopUp(tags, CmailMsg());
9359             free(tags);
9360           }
9361         }
9362     } else {
9363         /* Make something up, but don't display it now */
9364         SetGameInfo();
9365         TagsPopDown();
9366     }
9367
9368     if (cm == PositionDiagram) {
9369         int i, j;
9370         char *p;
9371         Board initial_position;
9372
9373         if (appData.debugMode)
9374           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9375
9376         if (!startedFromSetupPosition) {
9377             p = yy_text;
9378             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9379               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9380                 switch (*p) {
9381                   case '[':
9382                   case '-':
9383                   case ' ':
9384                   case '\t':
9385                   case '\n':
9386                   case '\r':
9387                     break;
9388                   default:
9389                     initial_position[i][j++] = CharToPiece(*p);
9390                     break;
9391                 }
9392             while (*p == ' ' || *p == '\t' ||
9393                    *p == '\n' || *p == '\r') p++;
9394         
9395             if (strncmp(p, "black", strlen("black"))==0)
9396               blackPlaysFirst = TRUE;
9397             else
9398               blackPlaysFirst = FALSE;
9399             startedFromSetupPosition = TRUE;
9400         
9401             CopyBoard(boards[0], initial_position);
9402             if (blackPlaysFirst) {
9403                 currentMove = forwardMostMove = backwardMostMove = 1;
9404                 CopyBoard(boards[1], initial_position);
9405                 strcpy(moveList[0], "");
9406                 strcpy(parseList[0], "");
9407                 timeRemaining[0][1] = whiteTimeRemaining;
9408                 timeRemaining[1][1] = blackTimeRemaining;
9409                 if (commentList[0] != NULL) {
9410                     commentList[1] = commentList[0];
9411                     commentList[0] = NULL;
9412                 }
9413             } else {
9414                 currentMove = forwardMostMove = backwardMostMove = 0;
9415             }
9416         }
9417         yyboardindex = forwardMostMove;
9418         cm = (ChessMove) yylex();
9419     }
9420
9421     if (first.pr == NoProc) {
9422         StartChessProgram(&first);
9423     }
9424     InitChessProgram(&first, FALSE);
9425     SendToProgram("force\n", &first);
9426     if (startedFromSetupPosition) {
9427         SendBoard(&first, forwardMostMove);
9428     if (appData.debugMode) {
9429         fprintf(debugFP, "Load Game\n");
9430     }
9431         DisplayBothClocks();
9432     }      
9433
9434     /* [HGM] server: flag to write setup moves in broadcast file as one */
9435     loadFlag = appData.suppressLoadMoves;
9436
9437     while (cm == Comment) {
9438         char *p;
9439         if (appData.debugMode) 
9440           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9441         p = yy_text;
9442         if (*p == '{' || *p == '[' || *p == '(') {
9443             p[strlen(p) - 1] = NULLCHAR;
9444             p++;
9445         }
9446         while (*p == '\n') p++;
9447         AppendComment(currentMove, p);
9448         yyboardindex = forwardMostMove;
9449         cm = (ChessMove) yylex();
9450     }
9451
9452     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9453         cm == WhiteWins || cm == BlackWins ||
9454         cm == GameIsDrawn || cm == GameUnfinished) {
9455         DisplayMessage("", _("No moves in game"));
9456         if (cmailMsgLoaded) {
9457             if (appData.debugMode)
9458               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9459             ClearHighlights();
9460             flipView = FALSE;
9461         }
9462         DrawPosition(FALSE, boards[currentMove]);
9463         DisplayBothClocks();
9464         gameMode = EditGame;
9465         ModeHighlight();
9466         gameFileFP = NULL;
9467         cmailOldMove = 0;
9468         return TRUE;
9469     }
9470
9471     // [HGM] PV info: routine tests if comment empty
9472     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9473         DisplayComment(currentMove - 1, commentList[currentMove]);
9474     }
9475     if (!matchMode && appData.timeDelay != 0) 
9476       DrawPosition(FALSE, boards[currentMove]);
9477
9478     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9479       programStats.ok_to_send = 1;
9480     }
9481
9482     /* if the first token after the PGN tags is a move
9483      * and not move number 1, retrieve it from the parser 
9484      */
9485     if (cm != MoveNumberOne)
9486         LoadGameOneMove(cm);
9487
9488     /* load the remaining moves from the file */
9489     while (LoadGameOneMove((ChessMove)0)) {
9490       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9491       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9492     }
9493
9494     /* rewind to the start of the game */
9495     currentMove = backwardMostMove;
9496
9497     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9498
9499     if (oldGameMode == AnalyzeFile ||
9500         oldGameMode == AnalyzeMode) {
9501       AnalyzeFileEvent();
9502     }
9503
9504     if (matchMode || appData.timeDelay == 0) {
9505       ToEndEvent();
9506       gameMode = EditGame;
9507       ModeHighlight();
9508     } else if (appData.timeDelay > 0) {
9509       AutoPlayGameLoop();
9510     }
9511
9512     if (appData.debugMode) 
9513         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9514
9515     loadFlag = 0; /* [HGM] true game starts */
9516     return TRUE;
9517 }
9518
9519 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9520 int
9521 ReloadPosition(offset)
9522      int offset;
9523 {
9524     int positionNumber = lastLoadPositionNumber + offset;
9525     if (lastLoadPositionFP == NULL) {
9526         DisplayError(_("No position has been loaded yet"), 0);
9527         return FALSE;
9528     }
9529     if (positionNumber <= 0) {
9530         DisplayError(_("Can't back up any further"), 0);
9531         return FALSE;
9532     }
9533     return LoadPosition(lastLoadPositionFP, positionNumber,
9534                         lastLoadPositionTitle);
9535 }
9536
9537 /* Load the nth position from the given file */
9538 int
9539 LoadPositionFromFile(filename, n, title)
9540      char *filename;
9541      int n;
9542      char *title;
9543 {
9544     FILE *f;
9545     char buf[MSG_SIZ];
9546
9547     if (strcmp(filename, "-") == 0) {
9548         return LoadPosition(stdin, n, "stdin");
9549     } else {
9550         f = fopen(filename, "rb");
9551         if (f == NULL) {
9552             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9553             DisplayError(buf, errno);
9554             return FALSE;
9555         } else {
9556             return LoadPosition(f, n, title);
9557         }
9558     }
9559 }
9560
9561 /* Load the nth position from the given open file, and close it */
9562 int
9563 LoadPosition(f, positionNumber, title)
9564      FILE *f;
9565      int positionNumber;
9566      char *title;
9567 {
9568     char *p, line[MSG_SIZ];
9569     Board initial_position;
9570     int i, j, fenMode, pn;
9571     
9572     if (gameMode == Training )
9573         SetTrainingModeOff();
9574
9575     if (gameMode != BeginningOfGame) {
9576         Reset(FALSE, TRUE);
9577     }
9578     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9579         fclose(lastLoadPositionFP);
9580     }
9581     if (positionNumber == 0) positionNumber = 1;
9582     lastLoadPositionFP = f;
9583     lastLoadPositionNumber = positionNumber;
9584     strcpy(lastLoadPositionTitle, title);
9585     if (first.pr == NoProc) {
9586       StartChessProgram(&first);
9587       InitChessProgram(&first, FALSE);
9588     }    
9589     pn = positionNumber;
9590     if (positionNumber < 0) {
9591         /* Negative position number means to seek to that byte offset */
9592         if (fseek(f, -positionNumber, 0) == -1) {
9593             DisplayError(_("Can't seek on position file"), 0);
9594             return FALSE;
9595         };
9596         pn = 1;
9597     } else {
9598         if (fseek(f, 0, 0) == -1) {
9599             if (f == lastLoadPositionFP ?
9600                 positionNumber == lastLoadPositionNumber + 1 :
9601                 positionNumber == 1) {
9602                 pn = 1;
9603             } else {
9604                 DisplayError(_("Can't seek on position file"), 0);
9605                 return FALSE;
9606             }
9607         }
9608     }
9609     /* See if this file is FEN or old-style xboard */
9610     if (fgets(line, MSG_SIZ, f) == NULL) {
9611         DisplayError(_("Position not found in file"), 0);
9612         return FALSE;
9613     }
9614     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9615     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9616
9617     if (pn >= 2) {
9618         if (fenMode || line[0] == '#') pn--;
9619         while (pn > 0) {
9620             /* skip positions before number pn */
9621             if (fgets(line, MSG_SIZ, f) == NULL) {
9622                 Reset(TRUE, TRUE);
9623                 DisplayError(_("Position not found in file"), 0);
9624                 return FALSE;
9625             }
9626             if (fenMode || line[0] == '#') pn--;
9627         }
9628     }
9629
9630     if (fenMode) {
9631         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9632             DisplayError(_("Bad FEN position in file"), 0);
9633             return FALSE;
9634         }
9635     } else {
9636         (void) fgets(line, MSG_SIZ, f);
9637         (void) fgets(line, MSG_SIZ, f);
9638     
9639         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9640             (void) fgets(line, MSG_SIZ, f);
9641             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9642                 if (*p == ' ')
9643                   continue;
9644                 initial_position[i][j++] = CharToPiece(*p);
9645             }
9646         }
9647     
9648         blackPlaysFirst = FALSE;
9649         if (!feof(f)) {
9650             (void) fgets(line, MSG_SIZ, f);
9651             if (strncmp(line, "black", strlen("black"))==0)
9652               blackPlaysFirst = TRUE;
9653         }
9654     }
9655     startedFromSetupPosition = TRUE;
9656     
9657     SendToProgram("force\n", &first);
9658     CopyBoard(boards[0], initial_position);
9659     if (blackPlaysFirst) {
9660         currentMove = forwardMostMove = backwardMostMove = 1;
9661         strcpy(moveList[0], "");
9662         strcpy(parseList[0], "");
9663         CopyBoard(boards[1], initial_position);
9664         DisplayMessage("", _("Black to play"));
9665     } else {
9666         currentMove = forwardMostMove = backwardMostMove = 0;
9667         DisplayMessage("", _("White to play"));
9668     }
9669           /* [HGM] copy FEN attributes as well */
9670           {   int i;
9671               initialRulePlies = FENrulePlies;
9672               epStatus[forwardMostMove] = FENepStatus;
9673               for( i=0; i< nrCastlingRights; i++ )
9674                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9675           }
9676     SendBoard(&first, forwardMostMove);
9677     if (appData.debugMode) {
9678 int i, j;
9679   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9680   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9681         fprintf(debugFP, "Load Position\n");
9682     }
9683
9684     if (positionNumber > 1) {
9685         sprintf(line, "%s %d", title, positionNumber);
9686         DisplayTitle(line);
9687     } else {
9688         DisplayTitle(title);
9689     }
9690     gameMode = EditGame;
9691     ModeHighlight();
9692     ResetClocks();
9693     timeRemaining[0][1] = whiteTimeRemaining;
9694     timeRemaining[1][1] = blackTimeRemaining;
9695     DrawPosition(FALSE, boards[currentMove]);
9696    
9697     return TRUE;
9698 }
9699
9700
9701 void
9702 CopyPlayerNameIntoFileName(dest, src)
9703      char **dest, *src;
9704 {
9705     while (*src != NULLCHAR && *src != ',') {
9706         if (*src == ' ') {
9707             *(*dest)++ = '_';
9708             src++;
9709         } else {
9710             *(*dest)++ = *src++;
9711         }
9712     }
9713 }
9714
9715 char *DefaultFileName(ext)
9716      char *ext;
9717 {
9718     static char def[MSG_SIZ];
9719     char *p;
9720
9721     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9722         p = def;
9723         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9724         *p++ = '-';
9725         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9726         *p++ = '.';
9727         strcpy(p, ext);
9728     } else {
9729         def[0] = NULLCHAR;
9730     }
9731     return def;
9732 }
9733
9734 /* Save the current game to the given file */
9735 int
9736 SaveGameToFile(filename, append)
9737      char *filename;
9738      int append;
9739 {
9740     FILE *f;
9741     char buf[MSG_SIZ];
9742
9743     if (strcmp(filename, "-") == 0) {
9744         return SaveGame(stdout, 0, NULL);
9745     } else {
9746         f = fopen(filename, append ? "a" : "w");
9747         if (f == NULL) {
9748             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9749             DisplayError(buf, errno);
9750             return FALSE;
9751         } else {
9752             return SaveGame(f, 0, NULL);
9753         }
9754     }
9755 }
9756
9757 char *
9758 SavePart(str)
9759      char *str;
9760 {
9761     static char buf[MSG_SIZ];
9762     char *p;
9763     
9764     p = strchr(str, ' ');
9765     if (p == NULL) return str;
9766     strncpy(buf, str, p - str);
9767     buf[p - str] = NULLCHAR;
9768     return buf;
9769 }
9770
9771 #define PGN_MAX_LINE 75
9772
9773 #define PGN_SIDE_WHITE  0
9774 #define PGN_SIDE_BLACK  1
9775
9776 /* [AS] */
9777 static int FindFirstMoveOutOfBook( int side )
9778 {
9779     int result = -1;
9780
9781     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9782         int index = backwardMostMove;
9783         int has_book_hit = 0;
9784
9785         if( (index % 2) != side ) {
9786             index++;
9787         }
9788
9789         while( index < forwardMostMove ) {
9790             /* Check to see if engine is in book */
9791             int depth = pvInfoList[index].depth;
9792             int score = pvInfoList[index].score;
9793             int in_book = 0;
9794
9795             if( depth <= 2 ) {
9796                 in_book = 1;
9797             }
9798             else if( score == 0 && depth == 63 ) {
9799                 in_book = 1; /* Zappa */
9800             }
9801             else if( score == 2 && depth == 99 ) {
9802                 in_book = 1; /* Abrok */
9803             }
9804
9805             has_book_hit += in_book;
9806
9807             if( ! in_book ) {
9808                 result = index;
9809
9810                 break;
9811             }
9812
9813             index += 2;
9814         }
9815     }
9816
9817     return result;
9818 }
9819
9820 /* [AS] */
9821 void GetOutOfBookInfo( char * buf )
9822 {
9823     int oob[2];
9824     int i;
9825     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9826
9827     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9828     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9829
9830     *buf = '\0';
9831
9832     if( oob[0] >= 0 || oob[1] >= 0 ) {
9833         for( i=0; i<2; i++ ) {
9834             int idx = oob[i];
9835
9836             if( idx >= 0 ) {
9837                 if( i > 0 && oob[0] >= 0 ) {
9838                     strcat( buf, "   " );
9839                 }
9840
9841                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9842                 sprintf( buf+strlen(buf), "%s%.2f", 
9843                     pvInfoList[idx].score >= 0 ? "+" : "",
9844                     pvInfoList[idx].score / 100.0 );
9845             }
9846         }
9847     }
9848 }
9849
9850 /* Save game in PGN style and close the file */
9851 int
9852 SaveGamePGN(f)
9853      FILE *f;
9854 {
9855     int i, offset, linelen, newblock;
9856     time_t tm;
9857 //    char *movetext;
9858     char numtext[32];
9859     int movelen, numlen, blank;
9860     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9861
9862     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9863     
9864     tm = time((time_t *) NULL);
9865     
9866     PrintPGNTags(f, &gameInfo);
9867     
9868     if (backwardMostMove > 0 || startedFromSetupPosition) {
9869         char *fen = PositionToFEN(backwardMostMove, NULL);
9870         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9871         fprintf(f, "\n{--------------\n");
9872         PrintPosition(f, backwardMostMove);
9873         fprintf(f, "--------------}\n");
9874         free(fen);
9875     }
9876     else {
9877         /* [AS] Out of book annotation */
9878         if( appData.saveOutOfBookInfo ) {
9879             char buf[64];
9880
9881             GetOutOfBookInfo( buf );
9882
9883             if( buf[0] != '\0' ) {
9884                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9885             }
9886         }
9887
9888         fprintf(f, "\n");
9889     }
9890
9891     i = backwardMostMove;
9892     linelen = 0;
9893     newblock = TRUE;
9894
9895     while (i < forwardMostMove) {
9896         /* Print comments preceding this move */
9897         if (commentList[i] != NULL) {
9898             if (linelen > 0) fprintf(f, "\n");
9899             fprintf(f, "{\n%s}\n", commentList[i]);
9900             linelen = 0;
9901             newblock = TRUE;
9902         }
9903
9904         /* Format move number */
9905         if ((i % 2) == 0) {
9906             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9907         } else {
9908             if (newblock) {
9909                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9910             } else {
9911                 numtext[0] = NULLCHAR;
9912             }
9913         }
9914         numlen = strlen(numtext);
9915         newblock = FALSE;
9916
9917         /* Print move number */
9918         blank = linelen > 0 && numlen > 0;
9919         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9920             fprintf(f, "\n");
9921             linelen = 0;
9922             blank = 0;
9923         }
9924         if (blank) {
9925             fprintf(f, " ");
9926             linelen++;
9927         }
9928         fprintf(f, "%s", numtext);
9929         linelen += numlen;
9930
9931         /* Get move */
9932         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9933         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9934
9935         /* Print move */
9936         blank = linelen > 0 && movelen > 0;
9937         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9938             fprintf(f, "\n");
9939             linelen = 0;
9940             blank = 0;
9941         }
9942         if (blank) {
9943             fprintf(f, " ");
9944             linelen++;
9945         }
9946         fprintf(f, "%s", move_buffer);
9947         linelen += movelen;
9948
9949         /* [AS] Add PV info if present */
9950         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9951             /* [HGM] add time */
9952             char buf[MSG_SIZ]; int seconds = 0;
9953
9954             if(i >= backwardMostMove) {
9955                 if(WhiteOnMove(i))
9956                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9957                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9958                 else
9959                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9960                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9961             }
9962             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9963
9964             if( seconds <= 0) buf[0] = 0; else
9965             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9966                 seconds = (seconds + 4)/10; // round to full seconds
9967                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9968                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9969             }
9970
9971             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9972                 pvInfoList[i].score >= 0 ? "+" : "",
9973                 pvInfoList[i].score / 100.0,
9974                 pvInfoList[i].depth,
9975                 buf );
9976
9977             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9978
9979             /* Print score/depth */
9980             blank = linelen > 0 && movelen > 0;
9981             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9982                 fprintf(f, "\n");
9983                 linelen = 0;
9984                 blank = 0;
9985             }
9986             if (blank) {
9987                 fprintf(f, " ");
9988                 linelen++;
9989             }
9990             fprintf(f, "%s", move_buffer);
9991             linelen += movelen;
9992         }
9993
9994         i++;
9995     }
9996     
9997     /* Start a new line */
9998     if (linelen > 0) fprintf(f, "\n");
9999
10000     /* Print comments after last move */
10001     if (commentList[i] != NULL) {
10002         fprintf(f, "{\n%s}\n", commentList[i]);
10003     }
10004
10005     /* Print result */
10006     if (gameInfo.resultDetails != NULL &&
10007         gameInfo.resultDetails[0] != NULLCHAR) {
10008         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10009                 PGNResult(gameInfo.result));
10010     } else {
10011         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10012     }
10013
10014     fclose(f);
10015     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10016     return TRUE;
10017 }
10018
10019 /* Save game in old style and close the file */
10020 int
10021 SaveGameOldStyle(f)
10022      FILE *f;
10023 {
10024     int i, offset;
10025     time_t tm;
10026     
10027     tm = time((time_t *) NULL);
10028     
10029     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10030     PrintOpponents(f);
10031     
10032     if (backwardMostMove > 0 || startedFromSetupPosition) {
10033         fprintf(f, "\n[--------------\n");
10034         PrintPosition(f, backwardMostMove);
10035         fprintf(f, "--------------]\n");
10036     } else {
10037         fprintf(f, "\n");
10038     }
10039
10040     i = backwardMostMove;
10041     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10042
10043     while (i < forwardMostMove) {
10044         if (commentList[i] != NULL) {
10045             fprintf(f, "[%s]\n", commentList[i]);
10046         }
10047
10048         if ((i % 2) == 1) {
10049             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10050             i++;
10051         } else {
10052             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10053             i++;
10054             if (commentList[i] != NULL) {
10055                 fprintf(f, "\n");
10056                 continue;
10057             }
10058             if (i >= forwardMostMove) {
10059                 fprintf(f, "\n");
10060                 break;
10061             }
10062             fprintf(f, "%s\n", parseList[i]);
10063             i++;
10064         }
10065     }
10066     
10067     if (commentList[i] != NULL) {
10068         fprintf(f, "[%s]\n", commentList[i]);
10069     }
10070
10071     /* This isn't really the old style, but it's close enough */
10072     if (gameInfo.resultDetails != NULL &&
10073         gameInfo.resultDetails[0] != NULLCHAR) {
10074         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10075                 gameInfo.resultDetails);
10076     } else {
10077         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10078     }
10079
10080     fclose(f);
10081     return TRUE;
10082 }
10083
10084 /* Save the current game to open file f and close the file */
10085 int
10086 SaveGame(f, dummy, dummy2)
10087      FILE *f;
10088      int dummy;
10089      char *dummy2;
10090 {
10091     if (gameMode == EditPosition) EditPositionDone();
10092     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10093     if (appData.oldSaveStyle)
10094       return SaveGameOldStyle(f);
10095     else
10096       return SaveGamePGN(f);
10097 }
10098
10099 /* Save the current position to the given file */
10100 int
10101 SavePositionToFile(filename)
10102      char *filename;
10103 {
10104     FILE *f;
10105     char buf[MSG_SIZ];
10106
10107     if (strcmp(filename, "-") == 0) {
10108         return SavePosition(stdout, 0, NULL);
10109     } else {
10110         f = fopen(filename, "a");
10111         if (f == NULL) {
10112             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10113             DisplayError(buf, errno);
10114             return FALSE;
10115         } else {
10116             SavePosition(f, 0, NULL);
10117             return TRUE;
10118         }
10119     }
10120 }
10121
10122 /* Save the current position to the given open file and close the file */
10123 int
10124 SavePosition(f, dummy, dummy2)
10125      FILE *f;
10126      int dummy;
10127      char *dummy2;
10128 {
10129     time_t tm;
10130     char *fen;
10131     
10132     if (appData.oldSaveStyle) {
10133         tm = time((time_t *) NULL);
10134     
10135         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10136         PrintOpponents(f);
10137         fprintf(f, "[--------------\n");
10138         PrintPosition(f, currentMove);
10139         fprintf(f, "--------------]\n");
10140     } else {
10141         fen = PositionToFEN(currentMove, NULL);
10142         fprintf(f, "%s\n", fen);
10143         free(fen);
10144     }
10145     fclose(f);
10146     return TRUE;
10147 }
10148
10149 void
10150 ReloadCmailMsgEvent(unregister)
10151      int unregister;
10152 {
10153 #if !WIN32
10154     static char *inFilename = NULL;
10155     static char *outFilename;
10156     int i;
10157     struct stat inbuf, outbuf;
10158     int status;
10159     
10160     /* Any registered moves are unregistered if unregister is set, */
10161     /* i.e. invoked by the signal handler */
10162     if (unregister) {
10163         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10164             cmailMoveRegistered[i] = FALSE;
10165             if (cmailCommentList[i] != NULL) {
10166                 free(cmailCommentList[i]);
10167                 cmailCommentList[i] = NULL;
10168             }
10169         }
10170         nCmailMovesRegistered = 0;
10171     }
10172
10173     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10174         cmailResult[i] = CMAIL_NOT_RESULT;
10175     }
10176     nCmailResults = 0;
10177
10178     if (inFilename == NULL) {
10179         /* Because the filenames are static they only get malloced once  */
10180         /* and they never get freed                                      */
10181         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10182         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10183
10184         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10185         sprintf(outFilename, "%s.out", appData.cmailGameName);
10186     }
10187     
10188     status = stat(outFilename, &outbuf);
10189     if (status < 0) {
10190         cmailMailedMove = FALSE;
10191     } else {
10192         status = stat(inFilename, &inbuf);
10193         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10194     }
10195     
10196     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10197        counts the games, notes how each one terminated, etc.
10198        
10199        It would be nice to remove this kludge and instead gather all
10200        the information while building the game list.  (And to keep it
10201        in the game list nodes instead of having a bunch of fixed-size
10202        parallel arrays.)  Note this will require getting each game's
10203        termination from the PGN tags, as the game list builder does
10204        not process the game moves.  --mann
10205        */
10206     cmailMsgLoaded = TRUE;
10207     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10208     
10209     /* Load first game in the file or popup game menu */
10210     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10211
10212 #endif /* !WIN32 */
10213     return;
10214 }
10215
10216 int
10217 RegisterMove()
10218 {
10219     FILE *f;
10220     char string[MSG_SIZ];
10221
10222     if (   cmailMailedMove
10223         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10224         return TRUE;            /* Allow free viewing  */
10225     }
10226
10227     /* Unregister move to ensure that we don't leave RegisterMove        */
10228     /* with the move registered when the conditions for registering no   */
10229     /* longer hold                                                       */
10230     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10231         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10232         nCmailMovesRegistered --;
10233
10234         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10235           {
10236               free(cmailCommentList[lastLoadGameNumber - 1]);
10237               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10238           }
10239     }
10240
10241     if (cmailOldMove == -1) {
10242         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10243         return FALSE;
10244     }
10245
10246     if (currentMove > cmailOldMove + 1) {
10247         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10248         return FALSE;
10249     }
10250
10251     if (currentMove < cmailOldMove) {
10252         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10253         return FALSE;
10254     }
10255
10256     if (forwardMostMove > currentMove) {
10257         /* Silently truncate extra moves */
10258         TruncateGame();
10259     }
10260
10261     if (   (currentMove == cmailOldMove + 1)
10262         || (   (currentMove == cmailOldMove)
10263             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10264                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10265         if (gameInfo.result != GameUnfinished) {
10266             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10267         }
10268
10269         if (commentList[currentMove] != NULL) {
10270             cmailCommentList[lastLoadGameNumber - 1]
10271               = StrSave(commentList[currentMove]);
10272         }
10273         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10274
10275         if (appData.debugMode)
10276           fprintf(debugFP, "Saving %s for game %d\n",
10277                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10278
10279         sprintf(string,
10280                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10281         
10282         f = fopen(string, "w");
10283         if (appData.oldSaveStyle) {
10284             SaveGameOldStyle(f); /* also closes the file */
10285             
10286             sprintf(string, "%s.pos.out", appData.cmailGameName);
10287             f = fopen(string, "w");
10288             SavePosition(f, 0, NULL); /* also closes the file */
10289         } else {
10290             fprintf(f, "{--------------\n");
10291             PrintPosition(f, currentMove);
10292             fprintf(f, "--------------}\n\n");
10293             
10294             SaveGame(f, 0, NULL); /* also closes the file*/
10295         }
10296         
10297         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10298         nCmailMovesRegistered ++;
10299     } else if (nCmailGames == 1) {
10300         DisplayError(_("You have not made a move yet"), 0);
10301         return FALSE;
10302     }
10303
10304     return TRUE;
10305 }
10306
10307 void
10308 MailMoveEvent()
10309 {
10310 #if !WIN32
10311     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10312     FILE *commandOutput;
10313     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10314     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10315     int nBuffers;
10316     int i;
10317     int archived;
10318     char *arcDir;
10319
10320     if (! cmailMsgLoaded) {
10321         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10322         return;
10323     }
10324
10325     if (nCmailGames == nCmailResults) {
10326         DisplayError(_("No unfinished games"), 0);
10327         return;
10328     }
10329
10330 #if CMAIL_PROHIBIT_REMAIL
10331     if (cmailMailedMove) {
10332         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);
10333         DisplayError(msg, 0);
10334         return;
10335     }
10336 #endif
10337
10338     if (! (cmailMailedMove || RegisterMove())) return;
10339     
10340     if (   cmailMailedMove
10341         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10342         sprintf(string, partCommandString,
10343                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10344         commandOutput = popen(string, "r");
10345
10346         if (commandOutput == NULL) {
10347             DisplayError(_("Failed to invoke cmail"), 0);
10348         } else {
10349             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10350                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10351             }
10352             if (nBuffers > 1) {
10353                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10354                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10355                 nBytes = MSG_SIZ - 1;
10356             } else {
10357                 (void) memcpy(msg, buffer, nBytes);
10358             }
10359             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10360
10361             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10362                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10363
10364                 archived = TRUE;
10365                 for (i = 0; i < nCmailGames; i ++) {
10366                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10367                         archived = FALSE;
10368                     }
10369                 }
10370                 if (   archived
10371                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10372                         != NULL)) {
10373                     sprintf(buffer, "%s/%s.%s.archive",
10374                             arcDir,
10375                             appData.cmailGameName,
10376                             gameInfo.date);
10377                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10378                     cmailMsgLoaded = FALSE;
10379                 }
10380             }
10381
10382             DisplayInformation(msg);
10383             pclose(commandOutput);
10384         }
10385     } else {
10386         if ((*cmailMsg) != '\0') {
10387             DisplayInformation(cmailMsg);
10388         }
10389     }
10390
10391     return;
10392 #endif /* !WIN32 */
10393 }
10394
10395 char *
10396 CmailMsg()
10397 {
10398 #if WIN32
10399     return NULL;
10400 #else
10401     int  prependComma = 0;
10402     char number[5];
10403     char string[MSG_SIZ];       /* Space for game-list */
10404     int  i;
10405     
10406     if (!cmailMsgLoaded) return "";
10407
10408     if (cmailMailedMove) {
10409         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10410     } else {
10411         /* Create a list of games left */
10412         sprintf(string, "[");
10413         for (i = 0; i < nCmailGames; i ++) {
10414             if (! (   cmailMoveRegistered[i]
10415                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10416                 if (prependComma) {
10417                     sprintf(number, ",%d", i + 1);
10418                 } else {
10419                     sprintf(number, "%d", i + 1);
10420                     prependComma = 1;
10421                 }
10422                 
10423                 strcat(string, number);
10424             }
10425         }
10426         strcat(string, "]");
10427
10428         if (nCmailMovesRegistered + nCmailResults == 0) {
10429             switch (nCmailGames) {
10430               case 1:
10431                 sprintf(cmailMsg,
10432                         _("Still need to make move for game\n"));
10433                 break;
10434                 
10435               case 2:
10436                 sprintf(cmailMsg,
10437                         _("Still need to make moves for both games\n"));
10438                 break;
10439                 
10440               default:
10441                 sprintf(cmailMsg,
10442                         _("Still need to make moves for all %d games\n"),
10443                         nCmailGames);
10444                 break;
10445             }
10446         } else {
10447             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10448               case 1:
10449                 sprintf(cmailMsg,
10450                         _("Still need to make a move for game %s\n"),
10451                         string);
10452                 break;
10453                 
10454               case 0:
10455                 if (nCmailResults == nCmailGames) {
10456                     sprintf(cmailMsg, _("No unfinished games\n"));
10457                 } else {
10458                     sprintf(cmailMsg, _("Ready to send mail\n"));
10459                 }
10460                 break;
10461                 
10462               default:
10463                 sprintf(cmailMsg,
10464                         _("Still need to make moves for games %s\n"),
10465                         string);
10466             }
10467         }
10468     }
10469     return cmailMsg;
10470 #endif /* WIN32 */
10471 }
10472
10473 void
10474 ResetGameEvent()
10475 {
10476     if (gameMode == Training)
10477       SetTrainingModeOff();
10478
10479     Reset(TRUE, TRUE);
10480     cmailMsgLoaded = FALSE;
10481     if (appData.icsActive) {
10482       SendToICS(ics_prefix);
10483       SendToICS("refresh\n");
10484     }
10485 }
10486
10487 void
10488 ExitEvent(status)
10489      int status;
10490 {
10491     exiting++;
10492     if (exiting > 2) {
10493       /* Give up on clean exit */
10494       exit(status);
10495     }
10496     if (exiting > 1) {
10497       /* Keep trying for clean exit */
10498       return;
10499     }
10500
10501     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10502
10503     if (telnetISR != NULL) {
10504       RemoveInputSource(telnetISR);
10505     }
10506     if (icsPR != NoProc) {
10507       DestroyChildProcess(icsPR, TRUE);
10508     }
10509
10510     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10511     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10512
10513     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10514     /* make sure this other one finishes before killing it!                  */
10515     if(endingGame) { int count = 0;
10516         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10517         while(endingGame && count++ < 10) DoSleep(1);
10518         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10519     }
10520
10521     /* Kill off chess programs */
10522     if (first.pr != NoProc) {
10523         ExitAnalyzeMode();
10524         
10525         DoSleep( appData.delayBeforeQuit );
10526         SendToProgram("quit\n", &first);
10527         DoSleep( appData.delayAfterQuit );
10528         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10529     }
10530     if (second.pr != NoProc) {
10531         DoSleep( appData.delayBeforeQuit );
10532         SendToProgram("quit\n", &second);
10533         DoSleep( appData.delayAfterQuit );
10534         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10535     }
10536     if (first.isr != NULL) {
10537         RemoveInputSource(first.isr);
10538     }
10539     if (second.isr != NULL) {
10540         RemoveInputSource(second.isr);
10541     }
10542
10543     ShutDownFrontEnd();
10544     exit(status);
10545 }
10546
10547 void
10548 PauseEvent()
10549 {
10550     if (appData.debugMode)
10551         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10552     if (pausing) {
10553         pausing = FALSE;
10554         ModeHighlight();
10555         if (gameMode == MachinePlaysWhite ||
10556             gameMode == MachinePlaysBlack) {
10557             StartClocks();
10558         } else {
10559             DisplayBothClocks();
10560         }
10561         if (gameMode == PlayFromGameFile) {
10562             if (appData.timeDelay >= 0) 
10563                 AutoPlayGameLoop();
10564         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10565             Reset(FALSE, TRUE);
10566             SendToICS(ics_prefix);
10567             SendToICS("refresh\n");
10568         } else if (currentMove < forwardMostMove) {
10569             ForwardInner(forwardMostMove);
10570         }
10571         pauseExamInvalid = FALSE;
10572     } else {
10573         switch (gameMode) {
10574           default:
10575             return;
10576           case IcsExamining:
10577             pauseExamForwardMostMove = forwardMostMove;
10578             pauseExamInvalid = FALSE;
10579             /* fall through */
10580           case IcsObserving:
10581           case IcsPlayingWhite:
10582           case IcsPlayingBlack:
10583             pausing = TRUE;
10584             ModeHighlight();
10585             return;
10586           case PlayFromGameFile:
10587             (void) StopLoadGameTimer();
10588             pausing = TRUE;
10589             ModeHighlight();
10590             break;
10591           case BeginningOfGame:
10592             if (appData.icsActive) return;
10593             /* else fall through */
10594           case MachinePlaysWhite:
10595           case MachinePlaysBlack:
10596           case TwoMachinesPlay:
10597             if (forwardMostMove == 0)
10598               return;           /* don't pause if no one has moved */
10599             if ((gameMode == MachinePlaysWhite &&
10600                  !WhiteOnMove(forwardMostMove)) ||
10601                 (gameMode == MachinePlaysBlack &&
10602                  WhiteOnMove(forwardMostMove))) {
10603                 StopClocks();
10604             }
10605             pausing = TRUE;
10606             ModeHighlight();
10607             break;
10608         }
10609     }
10610 }
10611
10612 void
10613 EditCommentEvent()
10614 {
10615     char title[MSG_SIZ];
10616
10617     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10618         strcpy(title, _("Edit comment"));
10619     } else {
10620         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10621                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10622                 parseList[currentMove - 1]);
10623     }
10624
10625     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10626 }
10627
10628
10629 void
10630 EditTagsEvent()
10631 {
10632     char *tags = PGNTags(&gameInfo);
10633     EditTagsPopUp(tags);
10634     free(tags);
10635 }
10636
10637 void
10638 AnalyzeModeEvent()
10639 {
10640     if (appData.noChessProgram || gameMode == AnalyzeMode)
10641       return;
10642
10643     if (gameMode != AnalyzeFile) {
10644         if (!appData.icsEngineAnalyze) {
10645                EditGameEvent();
10646                if (gameMode != EditGame) return;
10647         }
10648         ResurrectChessProgram();
10649         SendToProgram("analyze\n", &first);
10650         first.analyzing = TRUE;
10651         /*first.maybeThinking = TRUE;*/
10652         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10653         EngineOutputPopUp();
10654     }
10655     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10656     pausing = FALSE;
10657     ModeHighlight();
10658     SetGameInfo();
10659
10660     StartAnalysisClock();
10661     GetTimeMark(&lastNodeCountTime);
10662     lastNodeCount = 0;
10663 }
10664
10665 void
10666 AnalyzeFileEvent()
10667 {
10668     if (appData.noChessProgram || gameMode == AnalyzeFile)
10669       return;
10670
10671     if (gameMode != AnalyzeMode) {
10672         EditGameEvent();
10673         if (gameMode != EditGame) return;
10674         ResurrectChessProgram();
10675         SendToProgram("analyze\n", &first);
10676         first.analyzing = TRUE;
10677         /*first.maybeThinking = TRUE;*/
10678         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10679         EngineOutputPopUp();
10680     }
10681     gameMode = AnalyzeFile;
10682     pausing = FALSE;
10683     ModeHighlight();
10684     SetGameInfo();
10685
10686     StartAnalysisClock();
10687     GetTimeMark(&lastNodeCountTime);
10688     lastNodeCount = 0;
10689 }
10690
10691 void
10692 MachineWhiteEvent()
10693 {
10694     char buf[MSG_SIZ];
10695     char *bookHit = NULL;
10696
10697     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10698       return;
10699
10700
10701     if (gameMode == PlayFromGameFile || 
10702         gameMode == TwoMachinesPlay  || 
10703         gameMode == Training         || 
10704         gameMode == AnalyzeMode      || 
10705         gameMode == EndOfGame)
10706         EditGameEvent();
10707
10708     if (gameMode == EditPosition) 
10709         EditPositionDone();
10710
10711     if (!WhiteOnMove(currentMove)) {
10712         DisplayError(_("It is not White's turn"), 0);
10713         return;
10714     }
10715   
10716     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10717       ExitAnalyzeMode();
10718
10719     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10720         gameMode == AnalyzeFile)
10721         TruncateGame();
10722
10723     ResurrectChessProgram();    /* in case it isn't running */
10724     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10725         gameMode = MachinePlaysWhite;
10726         ResetClocks();
10727     } else
10728     gameMode = MachinePlaysWhite;
10729     pausing = FALSE;
10730     ModeHighlight();
10731     SetGameInfo();
10732     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10733     DisplayTitle(buf);
10734     if (first.sendName) {
10735       sprintf(buf, "name %s\n", gameInfo.black);
10736       SendToProgram(buf, &first);
10737     }
10738     if (first.sendTime) {
10739       if (first.useColors) {
10740         SendToProgram("black\n", &first); /*gnu kludge*/
10741       }
10742       SendTimeRemaining(&first, TRUE);
10743     }
10744     if (first.useColors) {
10745       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10746     }
10747     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10748     SetMachineThinkingEnables();
10749     first.maybeThinking = TRUE;
10750     StartClocks();
10751     firstMove = FALSE;
10752
10753     if (appData.autoFlipView && !flipView) {
10754       flipView = !flipView;
10755       DrawPosition(FALSE, NULL);
10756       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10757     }
10758
10759     if(bookHit) { // [HGM] book: simulate book reply
10760         static char bookMove[MSG_SIZ]; // a bit generous?
10761
10762         programStats.nodes = programStats.depth = programStats.time = 
10763         programStats.score = programStats.got_only_move = 0;
10764         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10765
10766         strcpy(bookMove, "move ");
10767         strcat(bookMove, bookHit);
10768         HandleMachineMove(bookMove, &first);
10769     }
10770 }
10771
10772 void
10773 MachineBlackEvent()
10774 {
10775     char buf[MSG_SIZ];
10776    char *bookHit = NULL;
10777
10778     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10779         return;
10780
10781
10782     if (gameMode == PlayFromGameFile || 
10783         gameMode == TwoMachinesPlay  || 
10784         gameMode == Training         || 
10785         gameMode == AnalyzeMode      || 
10786         gameMode == EndOfGame)
10787         EditGameEvent();
10788
10789     if (gameMode == EditPosition) 
10790         EditPositionDone();
10791
10792     if (WhiteOnMove(currentMove)) {
10793         DisplayError(_("It is not Black's turn"), 0);
10794         return;
10795     }
10796     
10797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10798       ExitAnalyzeMode();
10799
10800     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10801         gameMode == AnalyzeFile)
10802         TruncateGame();
10803
10804     ResurrectChessProgram();    /* in case it isn't running */
10805     gameMode = MachinePlaysBlack;
10806     pausing = FALSE;
10807     ModeHighlight();
10808     SetGameInfo();
10809     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10810     DisplayTitle(buf);
10811     if (first.sendName) {
10812       sprintf(buf, "name %s\n", gameInfo.white);
10813       SendToProgram(buf, &first);
10814     }
10815     if (first.sendTime) {
10816       if (first.useColors) {
10817         SendToProgram("white\n", &first); /*gnu kludge*/
10818       }
10819       SendTimeRemaining(&first, FALSE);
10820     }
10821     if (first.useColors) {
10822       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10823     }
10824     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10825     SetMachineThinkingEnables();
10826     first.maybeThinking = TRUE;
10827     StartClocks();
10828
10829     if (appData.autoFlipView && flipView) {
10830       flipView = !flipView;
10831       DrawPosition(FALSE, NULL);
10832       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10833     }
10834     if(bookHit) { // [HGM] book: simulate book reply
10835         static char bookMove[MSG_SIZ]; // a bit generous?
10836
10837         programStats.nodes = programStats.depth = programStats.time = 
10838         programStats.score = programStats.got_only_move = 0;
10839         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10840
10841         strcpy(bookMove, "move ");
10842         strcat(bookMove, bookHit);
10843         HandleMachineMove(bookMove, &first);
10844     }
10845 }
10846
10847
10848 void
10849 DisplayTwoMachinesTitle()
10850 {
10851     char buf[MSG_SIZ];
10852     if (appData.matchGames > 0) {
10853         if (first.twoMachinesColor[0] == 'w') {
10854             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10855                     gameInfo.white, gameInfo.black,
10856                     first.matchWins, second.matchWins,
10857                     matchGame - 1 - (first.matchWins + second.matchWins));
10858         } else {
10859             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10860                     gameInfo.white, gameInfo.black,
10861                     second.matchWins, first.matchWins,
10862                     matchGame - 1 - (first.matchWins + second.matchWins));
10863         }
10864     } else {
10865         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10866     }
10867     DisplayTitle(buf);
10868 }
10869
10870 void
10871 TwoMachinesEvent P((void))
10872 {
10873     int i;
10874     char buf[MSG_SIZ];
10875     ChessProgramState *onmove;
10876     char *bookHit = NULL;
10877     
10878     if (appData.noChessProgram) return;
10879
10880     switch (gameMode) {
10881       case TwoMachinesPlay:
10882         return;
10883       case MachinePlaysWhite:
10884       case MachinePlaysBlack:
10885         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10886             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10887             return;
10888         }
10889         /* fall through */
10890       case BeginningOfGame:
10891       case PlayFromGameFile:
10892       case EndOfGame:
10893         EditGameEvent();
10894         if (gameMode != EditGame) return;
10895         break;
10896       case EditPosition:
10897         EditPositionDone();
10898         break;
10899       case AnalyzeMode:
10900       case AnalyzeFile:
10901         ExitAnalyzeMode();
10902         break;
10903       case EditGame:
10904       default:
10905         break;
10906     }
10907
10908     forwardMostMove = currentMove;
10909     ResurrectChessProgram();    /* in case first program isn't running */
10910
10911     if (second.pr == NULL) {
10912         StartChessProgram(&second);
10913         if (second.protocolVersion == 1) {
10914           TwoMachinesEventIfReady();
10915         } else {
10916           /* kludge: allow timeout for initial "feature" command */
10917           FreezeUI();
10918           DisplayMessage("", _("Starting second chess program"));
10919           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10920         }
10921         return;
10922     }
10923     DisplayMessage("", "");
10924     InitChessProgram(&second, FALSE);
10925     SendToProgram("force\n", &second);
10926     if (startedFromSetupPosition) {
10927         SendBoard(&second, backwardMostMove);
10928     if (appData.debugMode) {
10929         fprintf(debugFP, "Two Machines\n");
10930     }
10931     }
10932     for (i = backwardMostMove; i < forwardMostMove; i++) {
10933         SendMoveToProgram(i, &second);
10934     }
10935
10936     gameMode = TwoMachinesPlay;
10937     pausing = FALSE;
10938     ModeHighlight();
10939     SetGameInfo();
10940     DisplayTwoMachinesTitle();
10941     firstMove = TRUE;
10942     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10943         onmove = &first;
10944     } else {
10945         onmove = &second;
10946     }
10947
10948     SendToProgram(first.computerString, &first);
10949     if (first.sendName) {
10950       sprintf(buf, "name %s\n", second.tidy);
10951       SendToProgram(buf, &first);
10952     }
10953     SendToProgram(second.computerString, &second);
10954     if (second.sendName) {
10955       sprintf(buf, "name %s\n", first.tidy);
10956       SendToProgram(buf, &second);
10957     }
10958
10959     ResetClocks();
10960     if (!first.sendTime || !second.sendTime) {
10961         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10962         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10963     }
10964     if (onmove->sendTime) {
10965       if (onmove->useColors) {
10966         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10967       }
10968       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10969     }
10970     if (onmove->useColors) {
10971       SendToProgram(onmove->twoMachinesColor, onmove);
10972     }
10973     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10974 //    SendToProgram("go\n", onmove);
10975     onmove->maybeThinking = TRUE;
10976     SetMachineThinkingEnables();
10977
10978     StartClocks();
10979
10980     if(bookHit) { // [HGM] book: simulate book reply
10981         static char bookMove[MSG_SIZ]; // a bit generous?
10982
10983         programStats.nodes = programStats.depth = programStats.time = 
10984         programStats.score = programStats.got_only_move = 0;
10985         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10986
10987         strcpy(bookMove, "move ");
10988         strcat(bookMove, bookHit);
10989         savedMessage = bookMove; // args for deferred call
10990         savedState = onmove;
10991         ScheduleDelayedEvent(DeferredBookMove, 1);
10992     }
10993 }
10994
10995 void
10996 TrainingEvent()
10997 {
10998     if (gameMode == Training) {
10999       SetTrainingModeOff();
11000       gameMode = PlayFromGameFile;
11001       DisplayMessage("", _("Training mode off"));
11002     } else {
11003       gameMode = Training;
11004       animateTraining = appData.animate;
11005
11006       /* make sure we are not already at the end of the game */
11007       if (currentMove < forwardMostMove) {
11008         SetTrainingModeOn();
11009         DisplayMessage("", _("Training mode on"));
11010       } else {
11011         gameMode = PlayFromGameFile;
11012         DisplayError(_("Already at end of game"), 0);
11013       }
11014     }
11015     ModeHighlight();
11016 }
11017
11018 void
11019 IcsClientEvent()
11020 {
11021     if (!appData.icsActive) return;
11022     switch (gameMode) {
11023       case IcsPlayingWhite:
11024       case IcsPlayingBlack:
11025       case IcsObserving:
11026       case IcsIdle:
11027       case BeginningOfGame:
11028       case IcsExamining:
11029         return;
11030
11031       case EditGame:
11032         break;
11033
11034       case EditPosition:
11035         EditPositionDone();
11036         break;
11037
11038       case AnalyzeMode:
11039       case AnalyzeFile:
11040         ExitAnalyzeMode();
11041         break;
11042         
11043       default:
11044         EditGameEvent();
11045         break;
11046     }
11047
11048     gameMode = IcsIdle;
11049     ModeHighlight();
11050     return;
11051 }
11052
11053
11054 void
11055 EditGameEvent()
11056 {
11057     int i;
11058
11059     switch (gameMode) {
11060       case Training:
11061         SetTrainingModeOff();
11062         break;
11063       case MachinePlaysWhite:
11064       case MachinePlaysBlack:
11065       case BeginningOfGame:
11066         SendToProgram("force\n", &first);
11067         SetUserThinkingEnables();
11068         break;
11069       case PlayFromGameFile:
11070         (void) StopLoadGameTimer();
11071         if (gameFileFP != NULL) {
11072             gameFileFP = NULL;
11073         }
11074         break;
11075       case EditPosition:
11076         EditPositionDone();
11077         break;
11078       case AnalyzeMode:
11079       case AnalyzeFile:
11080         ExitAnalyzeMode();
11081         SendToProgram("force\n", &first);
11082         break;
11083       case TwoMachinesPlay:
11084         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11085         ResurrectChessProgram();
11086         SetUserThinkingEnables();
11087         break;
11088       case EndOfGame:
11089         ResurrectChessProgram();
11090         break;
11091       case IcsPlayingBlack:
11092       case IcsPlayingWhite:
11093         DisplayError(_("Warning: You are still playing a game"), 0);
11094         break;
11095       case IcsObserving:
11096         DisplayError(_("Warning: You are still observing a game"), 0);
11097         break;
11098       case IcsExamining:
11099         DisplayError(_("Warning: You are still examining a game"), 0);
11100         break;
11101       case IcsIdle:
11102         break;
11103       case EditGame:
11104       default:
11105         return;
11106     }
11107     
11108     pausing = FALSE;
11109     StopClocks();
11110     first.offeredDraw = second.offeredDraw = 0;
11111
11112     if (gameMode == PlayFromGameFile) {
11113         whiteTimeRemaining = timeRemaining[0][currentMove];
11114         blackTimeRemaining = timeRemaining[1][currentMove];
11115         DisplayTitle("");
11116     }
11117
11118     if (gameMode == MachinePlaysWhite ||
11119         gameMode == MachinePlaysBlack ||
11120         gameMode == TwoMachinesPlay ||
11121         gameMode == EndOfGame) {
11122         i = forwardMostMove;
11123         while (i > currentMove) {
11124             SendToProgram("undo\n", &first);
11125             i--;
11126         }
11127         whiteTimeRemaining = timeRemaining[0][currentMove];
11128         blackTimeRemaining = timeRemaining[1][currentMove];
11129         DisplayBothClocks();
11130         if (whiteFlag || blackFlag) {
11131             whiteFlag = blackFlag = 0;
11132         }
11133         DisplayTitle("");
11134     }           
11135     
11136     gameMode = EditGame;
11137     ModeHighlight();
11138     SetGameInfo();
11139 }
11140
11141
11142 void
11143 EditPositionEvent()
11144 {
11145     if (gameMode == EditPosition) {
11146         EditGameEvent();
11147         return;
11148     }
11149     
11150     EditGameEvent();
11151     if (gameMode != EditGame) return;
11152     
11153     gameMode = EditPosition;
11154     ModeHighlight();
11155     SetGameInfo();
11156     if (currentMove > 0)
11157       CopyBoard(boards[0], boards[currentMove]);
11158     
11159     blackPlaysFirst = !WhiteOnMove(currentMove);
11160     ResetClocks();
11161     currentMove = forwardMostMove = backwardMostMove = 0;
11162     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11163     DisplayMove(-1);
11164 }
11165
11166 void
11167 ExitAnalyzeMode()
11168 {
11169     /* [DM] icsEngineAnalyze - possible call from other functions */
11170     if (appData.icsEngineAnalyze) {
11171         appData.icsEngineAnalyze = FALSE;
11172
11173         DisplayMessage("",_("Close ICS engine analyze..."));
11174     }
11175     if (first.analysisSupport && first.analyzing) {
11176       SendToProgram("exit\n", &first);
11177       first.analyzing = FALSE;
11178     }
11179     thinkOutput[0] = NULLCHAR;
11180 }
11181
11182 void
11183 EditPositionDone()
11184 {
11185     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11186
11187     startedFromSetupPosition = TRUE;
11188     InitChessProgram(&first, FALSE);
11189     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11190     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11191         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11192         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11193     } else castlingRights[0][2] = -1;
11194     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11195         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11196         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11197     } else castlingRights[0][5] = -1;
11198     SendToProgram("force\n", &first);
11199     if (blackPlaysFirst) {
11200         strcpy(moveList[0], "");
11201         strcpy(parseList[0], "");
11202         currentMove = forwardMostMove = backwardMostMove = 1;
11203         CopyBoard(boards[1], boards[0]);
11204         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11205         { int i;
11206           epStatus[1] = epStatus[0];
11207           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11208         }
11209     } else {
11210         currentMove = forwardMostMove = backwardMostMove = 0;
11211     }
11212     SendBoard(&first, forwardMostMove);
11213     if (appData.debugMode) {
11214         fprintf(debugFP, "EditPosDone\n");
11215     }
11216     DisplayTitle("");
11217     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11218     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11219     gameMode = EditGame;
11220     ModeHighlight();
11221     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11222     ClearHighlights(); /* [AS] */
11223 }
11224
11225 /* Pause for `ms' milliseconds */
11226 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11227 void
11228 TimeDelay(ms)
11229      long ms;
11230 {
11231     TimeMark m1, m2;
11232
11233     GetTimeMark(&m1);
11234     do {
11235         GetTimeMark(&m2);
11236     } while (SubtractTimeMarks(&m2, &m1) < ms);
11237 }
11238
11239 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11240 void
11241 SendMultiLineToICS(buf)
11242      char *buf;
11243 {
11244     char temp[MSG_SIZ+1], *p;
11245     int len;
11246
11247     len = strlen(buf);
11248     if (len > MSG_SIZ)
11249       len = MSG_SIZ;
11250   
11251     strncpy(temp, buf, len);
11252     temp[len] = 0;
11253
11254     p = temp;
11255     while (*p) {
11256         if (*p == '\n' || *p == '\r')
11257           *p = ' ';
11258         ++p;
11259     }
11260
11261     strcat(temp, "\n");
11262     SendToICS(temp);
11263     SendToPlayer(temp, strlen(temp));
11264 }
11265
11266 void
11267 SetWhiteToPlayEvent()
11268 {
11269     if (gameMode == EditPosition) {
11270         blackPlaysFirst = FALSE;
11271         DisplayBothClocks();    /* works because currentMove is 0 */
11272     } else if (gameMode == IcsExamining) {
11273         SendToICS(ics_prefix);
11274         SendToICS("tomove white\n");
11275     }
11276 }
11277
11278 void
11279 SetBlackToPlayEvent()
11280 {
11281     if (gameMode == EditPosition) {
11282         blackPlaysFirst = TRUE;
11283         currentMove = 1;        /* kludge */
11284         DisplayBothClocks();
11285         currentMove = 0;
11286     } else if (gameMode == IcsExamining) {
11287         SendToICS(ics_prefix);
11288         SendToICS("tomove black\n");
11289     }
11290 }
11291
11292 void
11293 EditPositionMenuEvent(selection, x, y)
11294      ChessSquare selection;
11295      int x, y;
11296 {
11297     char buf[MSG_SIZ];
11298     ChessSquare piece = boards[0][y][x];
11299
11300     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11301
11302     switch (selection) {
11303       case ClearBoard:
11304         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11305             SendToICS(ics_prefix);
11306             SendToICS("bsetup clear\n");
11307         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11308             SendToICS(ics_prefix);
11309             SendToICS("clearboard\n");
11310         } else {
11311             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11312                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11313                 for (y = 0; y < BOARD_HEIGHT; y++) {
11314                     if (gameMode == IcsExamining) {
11315                         if (boards[currentMove][y][x] != EmptySquare) {
11316                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11317                                     AAA + x, ONE + y);
11318                             SendToICS(buf);
11319                         }
11320                     } else {
11321                         boards[0][y][x] = p;
11322                     }
11323                 }
11324             }
11325         }
11326         if (gameMode == EditPosition) {
11327             DrawPosition(FALSE, boards[0]);
11328         }
11329         break;
11330
11331       case WhitePlay:
11332         SetWhiteToPlayEvent();
11333         break;
11334
11335       case BlackPlay:
11336         SetBlackToPlayEvent();
11337         break;
11338
11339       case EmptySquare:
11340         if (gameMode == IcsExamining) {
11341             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11342             SendToICS(buf);
11343         } else {
11344             boards[0][y][x] = EmptySquare;
11345             DrawPosition(FALSE, boards[0]);
11346         }
11347         break;
11348
11349       case PromotePiece:
11350         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11351            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11352             selection = (ChessSquare) (PROMOTED piece);
11353         } else if(piece == EmptySquare) selection = WhiteSilver;
11354         else selection = (ChessSquare)((int)piece - 1);
11355         goto defaultlabel;
11356
11357       case DemotePiece:
11358         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11359            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11360             selection = (ChessSquare) (DEMOTED piece);
11361         } else if(piece == EmptySquare) selection = BlackSilver;
11362         else selection = (ChessSquare)((int)piece + 1);       
11363         goto defaultlabel;
11364
11365       case WhiteQueen:
11366       case BlackQueen:
11367         if(gameInfo.variant == VariantShatranj ||
11368            gameInfo.variant == VariantXiangqi  ||
11369            gameInfo.variant == VariantCourier    )
11370             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11371         goto defaultlabel;
11372
11373       case WhiteKing:
11374       case BlackKing:
11375         if(gameInfo.variant == VariantXiangqi)
11376             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11377         if(gameInfo.variant == VariantKnightmate)
11378             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11379       default:
11380         defaultlabel:
11381         if (gameMode == IcsExamining) {
11382             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11383                     PieceToChar(selection), AAA + x, ONE + y);
11384             SendToICS(buf);
11385         } else {
11386             boards[0][y][x] = selection;
11387             DrawPosition(FALSE, boards[0]);
11388         }
11389         break;
11390     }
11391 }
11392
11393
11394 void
11395 DropMenuEvent(selection, x, y)
11396      ChessSquare selection;
11397      int x, y;
11398 {
11399     ChessMove moveType;
11400
11401     switch (gameMode) {
11402       case IcsPlayingWhite:
11403       case MachinePlaysBlack:
11404         if (!WhiteOnMove(currentMove)) {
11405             DisplayMoveError(_("It is Black's turn"));
11406             return;
11407         }
11408         moveType = WhiteDrop;
11409         break;
11410       case IcsPlayingBlack:
11411       case MachinePlaysWhite:
11412         if (WhiteOnMove(currentMove)) {
11413             DisplayMoveError(_("It is White's turn"));
11414             return;
11415         }
11416         moveType = BlackDrop;
11417         break;
11418       case EditGame:
11419         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11420         break;
11421       default:
11422         return;
11423     }
11424
11425     if (moveType == BlackDrop && selection < BlackPawn) {
11426       selection = (ChessSquare) ((int) selection
11427                                  + (int) BlackPawn - (int) WhitePawn);
11428     }
11429     if (boards[currentMove][y][x] != EmptySquare) {
11430         DisplayMoveError(_("That square is occupied"));
11431         return;
11432     }
11433
11434     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11435 }
11436
11437 void
11438 AcceptEvent()
11439 {
11440     /* Accept a pending offer of any kind from opponent */
11441     
11442     if (appData.icsActive) {
11443         SendToICS(ics_prefix);
11444         SendToICS("accept\n");
11445     } else if (cmailMsgLoaded) {
11446         if (currentMove == cmailOldMove &&
11447             commentList[cmailOldMove] != NULL &&
11448             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11449                    "Black offers a draw" : "White offers a draw")) {
11450             TruncateGame();
11451             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11452             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11453         } else {
11454             DisplayError(_("There is no pending offer on this move"), 0);
11455             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11456         }
11457     } else {
11458         /* Not used for offers from chess program */
11459     }
11460 }
11461
11462 void
11463 DeclineEvent()
11464 {
11465     /* Decline a pending offer of any kind from opponent */
11466     
11467     if (appData.icsActive) {
11468         SendToICS(ics_prefix);
11469         SendToICS("decline\n");
11470     } else if (cmailMsgLoaded) {
11471         if (currentMove == cmailOldMove &&
11472             commentList[cmailOldMove] != NULL &&
11473             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11474                    "Black offers a draw" : "White offers a draw")) {
11475 #ifdef NOTDEF
11476             AppendComment(cmailOldMove, "Draw declined");
11477             DisplayComment(cmailOldMove - 1, "Draw declined");
11478 #endif /*NOTDEF*/
11479         } else {
11480             DisplayError(_("There is no pending offer on this move"), 0);
11481         }
11482     } else {
11483         /* Not used for offers from chess program */
11484     }
11485 }
11486
11487 void
11488 RematchEvent()
11489 {
11490     /* Issue ICS rematch command */
11491     if (appData.icsActive) {
11492         SendToICS(ics_prefix);
11493         SendToICS("rematch\n");
11494     }
11495 }
11496
11497 void
11498 CallFlagEvent()
11499 {
11500     /* Call your opponent's flag (claim a win on time) */
11501     if (appData.icsActive) {
11502         SendToICS(ics_prefix);
11503         SendToICS("flag\n");
11504     } else {
11505         switch (gameMode) {
11506           default:
11507             return;
11508           case MachinePlaysWhite:
11509             if (whiteFlag) {
11510                 if (blackFlag)
11511                   GameEnds(GameIsDrawn, "Both players ran out of time",
11512                            GE_PLAYER);
11513                 else
11514                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11515             } else {
11516                 DisplayError(_("Your opponent is not out of time"), 0);
11517             }
11518             break;
11519           case MachinePlaysBlack:
11520             if (blackFlag) {
11521                 if (whiteFlag)
11522                   GameEnds(GameIsDrawn, "Both players ran out of time",
11523                            GE_PLAYER);
11524                 else
11525                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11526             } else {
11527                 DisplayError(_("Your opponent is not out of time"), 0);
11528             }
11529             break;
11530         }
11531     }
11532 }
11533
11534 void
11535 DrawEvent()
11536 {
11537     /* Offer draw or accept pending draw offer from opponent */
11538     
11539     if (appData.icsActive) {
11540         /* Note: tournament rules require draw offers to be
11541            made after you make your move but before you punch
11542            your clock.  Currently ICS doesn't let you do that;
11543            instead, you immediately punch your clock after making
11544            a move, but you can offer a draw at any time. */
11545         
11546         SendToICS(ics_prefix);
11547         SendToICS("draw\n");
11548     } else if (cmailMsgLoaded) {
11549         if (currentMove == cmailOldMove &&
11550             commentList[cmailOldMove] != NULL &&
11551             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11552                    "Black offers a draw" : "White offers a draw")) {
11553             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11554             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11555         } else if (currentMove == cmailOldMove + 1) {
11556             char *offer = WhiteOnMove(cmailOldMove) ?
11557               "White offers a draw" : "Black offers a draw";
11558             AppendComment(currentMove, offer);
11559             DisplayComment(currentMove - 1, offer);
11560             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11561         } else {
11562             DisplayError(_("You must make your move before offering a draw"), 0);
11563             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11564         }
11565     } else if (first.offeredDraw) {
11566         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11567     } else {
11568         if (first.sendDrawOffers) {
11569             SendToProgram("draw\n", &first);
11570             userOfferedDraw = TRUE;
11571         }
11572     }
11573 }
11574
11575 void
11576 AdjournEvent()
11577 {
11578     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11579     
11580     if (appData.icsActive) {
11581         SendToICS(ics_prefix);
11582         SendToICS("adjourn\n");
11583     } else {
11584         /* Currently GNU Chess doesn't offer or accept Adjourns */
11585     }
11586 }
11587
11588
11589 void
11590 AbortEvent()
11591 {
11592     /* Offer Abort or accept pending Abort offer from opponent */
11593     
11594     if (appData.icsActive) {
11595         SendToICS(ics_prefix);
11596         SendToICS("abort\n");
11597     } else {
11598         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11599     }
11600 }
11601
11602 void
11603 ResignEvent()
11604 {
11605     /* Resign.  You can do this even if it's not your turn. */
11606     
11607     if (appData.icsActive) {
11608         SendToICS(ics_prefix);
11609         SendToICS("resign\n");
11610     } else {
11611         switch (gameMode) {
11612           case MachinePlaysWhite:
11613             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11614             break;
11615           case MachinePlaysBlack:
11616             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11617             break;
11618           case EditGame:
11619             if (cmailMsgLoaded) {
11620                 TruncateGame();
11621                 if (WhiteOnMove(cmailOldMove)) {
11622                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11623                 } else {
11624                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11625                 }
11626                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11627             }
11628             break;
11629           default:
11630             break;
11631         }
11632     }
11633 }
11634
11635
11636 void
11637 StopObservingEvent()
11638 {
11639     /* Stop observing current games */
11640     SendToICS(ics_prefix);
11641     SendToICS("unobserve\n");
11642 }
11643
11644 void
11645 StopExaminingEvent()
11646 {
11647     /* Stop observing current game */
11648     SendToICS(ics_prefix);
11649     SendToICS("unexamine\n");
11650 }
11651
11652 void
11653 ForwardInner(target)
11654      int target;
11655 {
11656     int limit;
11657
11658     if (appData.debugMode)
11659         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11660                 target, currentMove, forwardMostMove);
11661
11662     if (gameMode == EditPosition)
11663       return;
11664
11665     if (gameMode == PlayFromGameFile && !pausing)
11666       PauseEvent();
11667     
11668     if (gameMode == IcsExamining && pausing)
11669       limit = pauseExamForwardMostMove;
11670     else
11671       limit = forwardMostMove;
11672     
11673     if (target > limit) target = limit;
11674
11675     if (target > 0 && moveList[target - 1][0]) {
11676         int fromX, fromY, toX, toY;
11677         toX = moveList[target - 1][2] - AAA;
11678         toY = moveList[target - 1][3] - ONE;
11679         if (moveList[target - 1][1] == '@') {
11680             if (appData.highlightLastMove) {
11681                 SetHighlights(-1, -1, toX, toY);
11682             }
11683         } else {
11684             fromX = moveList[target - 1][0] - AAA;
11685             fromY = moveList[target - 1][1] - ONE;
11686             if (target == currentMove + 1) {
11687                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11688             }
11689             if (appData.highlightLastMove) {
11690                 SetHighlights(fromX, fromY, toX, toY);
11691             }
11692         }
11693     }
11694     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11695         gameMode == Training || gameMode == PlayFromGameFile || 
11696         gameMode == AnalyzeFile) {
11697         while (currentMove < target) {
11698             SendMoveToProgram(currentMove++, &first);
11699         }
11700     } else {
11701         currentMove = target;
11702     }
11703     
11704     if (gameMode == EditGame || gameMode == EndOfGame) {
11705         whiteTimeRemaining = timeRemaining[0][currentMove];
11706         blackTimeRemaining = timeRemaining[1][currentMove];
11707     }
11708     DisplayBothClocks();
11709     DisplayMove(currentMove - 1);
11710     DrawPosition(FALSE, boards[currentMove]);
11711     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11712     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11713         DisplayComment(currentMove - 1, commentList[currentMove]);
11714     }
11715 }
11716
11717
11718 void
11719 ForwardEvent()
11720 {
11721     if (gameMode == IcsExamining && !pausing) {
11722         SendToICS(ics_prefix);
11723         SendToICS("forward\n");
11724     } else {
11725         ForwardInner(currentMove + 1);
11726     }
11727 }
11728
11729 void
11730 ToEndEvent()
11731 {
11732     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11733         /* to optimze, we temporarily turn off analysis mode while we feed
11734          * the remaining moves to the engine. Otherwise we get analysis output
11735          * after each move.
11736          */ 
11737         if (first.analysisSupport) {
11738           SendToProgram("exit\nforce\n", &first);
11739           first.analyzing = FALSE;
11740         }
11741     }
11742         
11743     if (gameMode == IcsExamining && !pausing) {
11744         SendToICS(ics_prefix);
11745         SendToICS("forward 999999\n");
11746     } else {
11747         ForwardInner(forwardMostMove);
11748     }
11749
11750     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11751         /* we have fed all the moves, so reactivate analysis mode */
11752         SendToProgram("analyze\n", &first);
11753         first.analyzing = TRUE;
11754         /*first.maybeThinking = TRUE;*/
11755         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11756     }
11757 }
11758
11759 void
11760 BackwardInner(target)
11761      int target;
11762 {
11763     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11764
11765     if (appData.debugMode)
11766         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11767                 target, currentMove, forwardMostMove);
11768
11769     if (gameMode == EditPosition) return;
11770     if (currentMove <= backwardMostMove) {
11771         ClearHighlights();
11772         DrawPosition(full_redraw, boards[currentMove]);
11773         return;
11774     }
11775     if (gameMode == PlayFromGameFile && !pausing)
11776       PauseEvent();
11777     
11778     if (moveList[target][0]) {
11779         int fromX, fromY, toX, toY;
11780         toX = moveList[target][2] - AAA;
11781         toY = moveList[target][3] - ONE;
11782         if (moveList[target][1] == '@') {
11783             if (appData.highlightLastMove) {
11784                 SetHighlights(-1, -1, toX, toY);
11785             }
11786         } else {
11787             fromX = moveList[target][0] - AAA;
11788             fromY = moveList[target][1] - ONE;
11789             if (target == currentMove - 1) {
11790                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11791             }
11792             if (appData.highlightLastMove) {
11793                 SetHighlights(fromX, fromY, toX, toY);
11794             }
11795         }
11796     }
11797     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11798         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11799         while (currentMove > target) {
11800             SendToProgram("undo\n", &first);
11801             currentMove--;
11802         }
11803     } else {
11804         currentMove = target;
11805     }
11806     
11807     if (gameMode == EditGame || gameMode == EndOfGame) {
11808         whiteTimeRemaining = timeRemaining[0][currentMove];
11809         blackTimeRemaining = timeRemaining[1][currentMove];
11810     }
11811     DisplayBothClocks();
11812     DisplayMove(currentMove - 1);
11813     DrawPosition(full_redraw, boards[currentMove]);
11814     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11815     // [HGM] PV info: routine tests if comment empty
11816     DisplayComment(currentMove - 1, commentList[currentMove]);
11817 }
11818
11819 void
11820 BackwardEvent()
11821 {
11822     if (gameMode == IcsExamining && !pausing) {
11823         SendToICS(ics_prefix);
11824         SendToICS("backward\n");
11825     } else {
11826         BackwardInner(currentMove - 1);
11827     }
11828 }
11829
11830 void
11831 ToStartEvent()
11832 {
11833     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11834         /* to optimize, we temporarily turn off analysis mode while we undo
11835          * all the moves. Otherwise we get analysis output after each undo.
11836          */ 
11837         if (first.analysisSupport) {
11838           SendToProgram("exit\nforce\n", &first);
11839           first.analyzing = FALSE;
11840         }
11841     }
11842
11843     if (gameMode == IcsExamining && !pausing) {
11844         SendToICS(ics_prefix);
11845         SendToICS("backward 999999\n");
11846     } else {
11847         BackwardInner(backwardMostMove);
11848     }
11849
11850     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11851         /* we have fed all the moves, so reactivate analysis mode */
11852         SendToProgram("analyze\n", &first);
11853         first.analyzing = TRUE;
11854         /*first.maybeThinking = TRUE;*/
11855         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11856     }
11857 }
11858
11859 void
11860 ToNrEvent(int to)
11861 {
11862   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11863   if (to >= forwardMostMove) to = forwardMostMove;
11864   if (to <= backwardMostMove) to = backwardMostMove;
11865   if (to < currentMove) {
11866     BackwardInner(to);
11867   } else {
11868     ForwardInner(to);
11869   }
11870 }
11871
11872 void
11873 RevertEvent()
11874 {
11875     if (gameMode != IcsExamining) {
11876         DisplayError(_("You are not examining a game"), 0);
11877         return;
11878     }
11879     if (pausing) {
11880         DisplayError(_("You can't revert while pausing"), 0);
11881         return;
11882     }
11883     SendToICS(ics_prefix);
11884     SendToICS("revert\n");
11885 }
11886
11887 void
11888 RetractMoveEvent()
11889 {
11890     switch (gameMode) {
11891       case MachinePlaysWhite:
11892       case MachinePlaysBlack:
11893         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11894             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11895             return;
11896         }
11897         if (forwardMostMove < 2) return;
11898         currentMove = forwardMostMove = forwardMostMove - 2;
11899         whiteTimeRemaining = timeRemaining[0][currentMove];
11900         blackTimeRemaining = timeRemaining[1][currentMove];
11901         DisplayBothClocks();
11902         DisplayMove(currentMove - 1);
11903         ClearHighlights();/*!! could figure this out*/
11904         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11905         SendToProgram("remove\n", &first);
11906         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11907         break;
11908
11909       case BeginningOfGame:
11910       default:
11911         break;
11912
11913       case IcsPlayingWhite:
11914       case IcsPlayingBlack:
11915         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11916             SendToICS(ics_prefix);
11917             SendToICS("takeback 2\n");
11918         } else {
11919             SendToICS(ics_prefix);
11920             SendToICS("takeback 1\n");
11921         }
11922         break;
11923     }
11924 }
11925
11926 void
11927 MoveNowEvent()
11928 {
11929     ChessProgramState *cps;
11930
11931     switch (gameMode) {
11932       case MachinePlaysWhite:
11933         if (!WhiteOnMove(forwardMostMove)) {
11934             DisplayError(_("It is your turn"), 0);
11935             return;
11936         }
11937         cps = &first;
11938         break;
11939       case MachinePlaysBlack:
11940         if (WhiteOnMove(forwardMostMove)) {
11941             DisplayError(_("It is your turn"), 0);
11942             return;
11943         }
11944         cps = &first;
11945         break;
11946       case TwoMachinesPlay:
11947         if (WhiteOnMove(forwardMostMove) ==
11948             (first.twoMachinesColor[0] == 'w')) {
11949             cps = &first;
11950         } else {
11951             cps = &second;
11952         }
11953         break;
11954       case BeginningOfGame:
11955       default:
11956         return;
11957     }
11958     SendToProgram("?\n", cps);
11959 }
11960
11961 void
11962 TruncateGameEvent()
11963 {
11964     EditGameEvent();
11965     if (gameMode != EditGame) return;
11966     TruncateGame();
11967 }
11968
11969 void
11970 TruncateGame()
11971 {
11972     if (forwardMostMove > currentMove) {
11973         if (gameInfo.resultDetails != NULL) {
11974             free(gameInfo.resultDetails);
11975             gameInfo.resultDetails = NULL;
11976             gameInfo.result = GameUnfinished;
11977         }
11978         forwardMostMove = currentMove;
11979         HistorySet(parseList, backwardMostMove, forwardMostMove,
11980                    currentMove-1);
11981     }
11982 }
11983
11984 void
11985 HintEvent()
11986 {
11987     if (appData.noChessProgram) return;
11988     switch (gameMode) {
11989       case MachinePlaysWhite:
11990         if (WhiteOnMove(forwardMostMove)) {
11991             DisplayError(_("Wait until your turn"), 0);
11992             return;
11993         }
11994         break;
11995       case BeginningOfGame:
11996       case MachinePlaysBlack:
11997         if (!WhiteOnMove(forwardMostMove)) {
11998             DisplayError(_("Wait until your turn"), 0);
11999             return;
12000         }
12001         break;
12002       default:
12003         DisplayError(_("No hint available"), 0);
12004         return;
12005     }
12006     SendToProgram("hint\n", &first);
12007     hintRequested = TRUE;
12008 }
12009
12010 void
12011 BookEvent()
12012 {
12013     if (appData.noChessProgram) return;
12014     switch (gameMode) {
12015       case MachinePlaysWhite:
12016         if (WhiteOnMove(forwardMostMove)) {
12017             DisplayError(_("Wait until your turn"), 0);
12018             return;
12019         }
12020         break;
12021       case BeginningOfGame:
12022       case MachinePlaysBlack:
12023         if (!WhiteOnMove(forwardMostMove)) {
12024             DisplayError(_("Wait until your turn"), 0);
12025             return;
12026         }
12027         break;
12028       case EditPosition:
12029         EditPositionDone();
12030         break;
12031       case TwoMachinesPlay:
12032         return;
12033       default:
12034         break;
12035     }
12036     SendToProgram("bk\n", &first);
12037     bookOutput[0] = NULLCHAR;
12038     bookRequested = TRUE;
12039 }
12040
12041 void
12042 AboutGameEvent()
12043 {
12044     char *tags = PGNTags(&gameInfo);
12045     TagsPopUp(tags, CmailMsg());
12046     free(tags);
12047 }
12048
12049 /* end button procedures */
12050
12051 void
12052 PrintPosition(fp, move)
12053      FILE *fp;
12054      int move;
12055 {
12056     int i, j;
12057     
12058     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12059         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12060             char c = PieceToChar(boards[move][i][j]);
12061             fputc(c == 'x' ? '.' : c, fp);
12062             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12063         }
12064     }
12065     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12066       fprintf(fp, "white to play\n");
12067     else
12068       fprintf(fp, "black to play\n");
12069 }
12070
12071 void
12072 PrintOpponents(fp)
12073      FILE *fp;
12074 {
12075     if (gameInfo.white != NULL) {
12076         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12077     } else {
12078         fprintf(fp, "\n");
12079     }
12080 }
12081
12082 /* Find last component of program's own name, using some heuristics */
12083 void
12084 TidyProgramName(prog, host, buf)
12085      char *prog, *host, buf[MSG_SIZ];
12086 {
12087     char *p, *q;
12088     int local = (strcmp(host, "localhost") == 0);
12089     while (!local && (p = strchr(prog, ';')) != NULL) {
12090         p++;
12091         while (*p == ' ') p++;
12092         prog = p;
12093     }
12094     if (*prog == '"' || *prog == '\'') {
12095         q = strchr(prog + 1, *prog);
12096     } else {
12097         q = strchr(prog, ' ');
12098     }
12099     if (q == NULL) q = prog + strlen(prog);
12100     p = q;
12101     while (p >= prog && *p != '/' && *p != '\\') p--;
12102     p++;
12103     if(p == prog && *p == '"') p++;
12104     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12105     memcpy(buf, p, q - p);
12106     buf[q - p] = NULLCHAR;
12107     if (!local) {
12108         strcat(buf, "@");
12109         strcat(buf, host);
12110     }
12111 }
12112
12113 char *
12114 TimeControlTagValue()
12115 {
12116     char buf[MSG_SIZ];
12117     if (!appData.clockMode) {
12118         strcpy(buf, "-");
12119     } else if (movesPerSession > 0) {
12120         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12121     } else if (timeIncrement == 0) {
12122         sprintf(buf, "%ld", timeControl/1000);
12123     } else {
12124         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12125     }
12126     return StrSave(buf);
12127 }
12128
12129 void
12130 SetGameInfo()
12131 {
12132     /* This routine is used only for certain modes */
12133     VariantClass v = gameInfo.variant;
12134     ClearGameInfo(&gameInfo);
12135     gameInfo.variant = v;
12136
12137     switch (gameMode) {
12138       case MachinePlaysWhite:
12139         gameInfo.event = StrSave( appData.pgnEventHeader );
12140         gameInfo.site = StrSave(HostName());
12141         gameInfo.date = PGNDate();
12142         gameInfo.round = StrSave("-");
12143         gameInfo.white = StrSave(first.tidy);
12144         gameInfo.black = StrSave(UserName());
12145         gameInfo.timeControl = TimeControlTagValue();
12146         break;
12147
12148       case MachinePlaysBlack:
12149         gameInfo.event = StrSave( appData.pgnEventHeader );
12150         gameInfo.site = StrSave(HostName());
12151         gameInfo.date = PGNDate();
12152         gameInfo.round = StrSave("-");
12153         gameInfo.white = StrSave(UserName());
12154         gameInfo.black = StrSave(first.tidy);
12155         gameInfo.timeControl = TimeControlTagValue();
12156         break;
12157
12158       case TwoMachinesPlay:
12159         gameInfo.event = StrSave( appData.pgnEventHeader );
12160         gameInfo.site = StrSave(HostName());
12161         gameInfo.date = PGNDate();
12162         if (matchGame > 0) {
12163             char buf[MSG_SIZ];
12164             sprintf(buf, "%d", matchGame);
12165             gameInfo.round = StrSave(buf);
12166         } else {
12167             gameInfo.round = StrSave("-");
12168         }
12169         if (first.twoMachinesColor[0] == 'w') {
12170             gameInfo.white = StrSave(first.tidy);
12171             gameInfo.black = StrSave(second.tidy);
12172         } else {
12173             gameInfo.white = StrSave(second.tidy);
12174             gameInfo.black = StrSave(first.tidy);
12175         }
12176         gameInfo.timeControl = TimeControlTagValue();
12177         break;
12178
12179       case EditGame:
12180         gameInfo.event = StrSave("Edited game");
12181         gameInfo.site = StrSave(HostName());
12182         gameInfo.date = PGNDate();
12183         gameInfo.round = StrSave("-");
12184         gameInfo.white = StrSave("-");
12185         gameInfo.black = StrSave("-");
12186         break;
12187
12188       case EditPosition:
12189         gameInfo.event = StrSave("Edited position");
12190         gameInfo.site = StrSave(HostName());
12191         gameInfo.date = PGNDate();
12192         gameInfo.round = StrSave("-");
12193         gameInfo.white = StrSave("-");
12194         gameInfo.black = StrSave("-");
12195         break;
12196
12197       case IcsPlayingWhite:
12198       case IcsPlayingBlack:
12199       case IcsObserving:
12200       case IcsExamining:
12201         break;
12202
12203       case PlayFromGameFile:
12204         gameInfo.event = StrSave("Game from non-PGN file");
12205         gameInfo.site = StrSave(HostName());
12206         gameInfo.date = PGNDate();
12207         gameInfo.round = StrSave("-");
12208         gameInfo.white = StrSave("?");
12209         gameInfo.black = StrSave("?");
12210         break;
12211
12212       default:
12213         break;
12214     }
12215 }
12216
12217 void
12218 ReplaceComment(index, text)
12219      int index;
12220      char *text;
12221 {
12222     int len;
12223
12224     while (*text == '\n') text++;
12225     len = strlen(text);
12226     while (len > 0 && text[len - 1] == '\n') len--;
12227
12228     if (commentList[index] != NULL)
12229       free(commentList[index]);
12230
12231     if (len == 0) {
12232         commentList[index] = NULL;
12233         return;
12234     }
12235     commentList[index] = (char *) malloc(len + 2);
12236     strncpy(commentList[index], text, len);
12237     commentList[index][len] = '\n';
12238     commentList[index][len + 1] = NULLCHAR;
12239 }
12240
12241 void
12242 CrushCRs(text)
12243      char *text;
12244 {
12245   char *p = text;
12246   char *q = text;
12247   char ch;
12248
12249   do {
12250     ch = *p++;
12251     if (ch == '\r') continue;
12252     *q++ = ch;
12253   } while (ch != '\0');
12254 }
12255
12256 void
12257 AppendComment(index, text)
12258      int index;
12259      char *text;
12260 {
12261     int oldlen, len;
12262     char *old;
12263
12264     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12265
12266     CrushCRs(text);
12267     while (*text == '\n') text++;
12268     len = strlen(text);
12269     while (len > 0 && text[len - 1] == '\n') len--;
12270
12271     if (len == 0) return;
12272
12273     if (commentList[index] != NULL) {
12274         old = commentList[index];
12275         oldlen = strlen(old);
12276         commentList[index] = (char *) malloc(oldlen + len + 2);
12277         strcpy(commentList[index], old);
12278         free(old);
12279         strncpy(&commentList[index][oldlen], text, len);
12280         commentList[index][oldlen + len] = '\n';
12281         commentList[index][oldlen + len + 1] = NULLCHAR;
12282     } else {
12283         commentList[index] = (char *) malloc(len + 2);
12284         strncpy(commentList[index], text, len);
12285         commentList[index][len] = '\n';
12286         commentList[index][len + 1] = NULLCHAR;
12287     }
12288 }
12289
12290 static char * FindStr( char * text, char * sub_text )
12291 {
12292     char * result = strstr( text, sub_text );
12293
12294     if( result != NULL ) {
12295         result += strlen( sub_text );
12296     }
12297
12298     return result;
12299 }
12300
12301 /* [AS] Try to extract PV info from PGN comment */
12302 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12303 char *GetInfoFromComment( int index, char * text )
12304 {
12305     char * sep = text;
12306
12307     if( text != NULL && index > 0 ) {
12308         int score = 0;
12309         int depth = 0;
12310         int time = -1, sec = 0, deci;
12311         char * s_eval = FindStr( text, "[%eval " );
12312         char * s_emt = FindStr( text, "[%emt " );
12313
12314         if( s_eval != NULL || s_emt != NULL ) {
12315             /* New style */
12316             char delim;
12317
12318             if( s_eval != NULL ) {
12319                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12320                     return text;
12321                 }
12322
12323                 if( delim != ']' ) {
12324                     return text;
12325                 }
12326             }
12327
12328             if( s_emt != NULL ) {
12329             }
12330         }
12331         else {
12332             /* We expect something like: [+|-]nnn.nn/dd */
12333             int score_lo = 0;
12334
12335             sep = strchr( text, '/' );
12336             if( sep == NULL || sep < (text+4) ) {
12337                 return text;
12338             }
12339
12340             time = -1; sec = -1; deci = -1;
12341             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12342                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12343                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12344                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12345                 return text;
12346             }
12347
12348             if( score_lo < 0 || score_lo >= 100 ) {
12349                 return text;
12350             }
12351
12352             if(sec >= 0) time = 600*time + 10*sec; else
12353             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12354
12355             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12356
12357             /* [HGM] PV time: now locate end of PV info */
12358             while( *++sep >= '0' && *sep <= '9'); // strip depth
12359             if(time >= 0)
12360             while( *++sep >= '0' && *sep <= '9'); // strip time
12361             if(sec >= 0)
12362             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12363             if(deci >= 0)
12364             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12365             while(*sep == ' ') sep++;
12366         }
12367
12368         if( depth <= 0 ) {
12369             return text;
12370         }
12371
12372         if( time < 0 ) {
12373             time = -1;
12374         }
12375
12376         pvInfoList[index-1].depth = depth;
12377         pvInfoList[index-1].score = score;
12378         pvInfoList[index-1].time  = 10*time; // centi-sec
12379     }
12380     return sep;
12381 }
12382
12383 void
12384 SendToProgram(message, cps)
12385      char *message;
12386      ChessProgramState *cps;
12387 {
12388     int count, outCount, error;
12389     char buf[MSG_SIZ];
12390
12391     if (cps->pr == NULL) return;
12392     Attention(cps);
12393     
12394     if (appData.debugMode) {
12395         TimeMark now;
12396         GetTimeMark(&now);
12397         fprintf(debugFP, "%ld >%-6s: %s", 
12398                 SubtractTimeMarks(&now, &programStartTime),
12399                 cps->which, message);
12400     }
12401     
12402     count = strlen(message);
12403     outCount = OutputToProcess(cps->pr, message, count, &error);
12404     if (outCount < count && !exiting 
12405                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12406         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12407         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12408             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12409                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12410                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12411             } else {
12412                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12413             }
12414             gameInfo.resultDetails = StrSave(buf);
12415         }
12416         DisplayFatalError(buf, error, 1);
12417     }
12418 }
12419
12420 void
12421 ReceiveFromProgram(isr, closure, message, count, error)
12422      InputSourceRef isr;
12423      VOIDSTAR closure;
12424      char *message;
12425      int count;
12426      int error;
12427 {
12428     char *end_str;
12429     char buf[MSG_SIZ];
12430     ChessProgramState *cps = (ChessProgramState *)closure;
12431
12432     if (isr != cps->isr) return; /* Killed intentionally */
12433     if (count <= 0) {
12434         if (count == 0) {
12435             sprintf(buf,
12436                     _("Error: %s chess program (%s) exited unexpectedly"),
12437                     cps->which, cps->program);
12438         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12439                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12440                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12441                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12442                 } else {
12443                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12444                 }
12445                 gameInfo.resultDetails = StrSave(buf);
12446             }
12447             RemoveInputSource(cps->isr);
12448             DisplayFatalError(buf, 0, 1);
12449         } else {
12450             sprintf(buf,
12451                     _("Error reading from %s chess program (%s)"),
12452                     cps->which, cps->program);
12453             RemoveInputSource(cps->isr);
12454
12455             /* [AS] Program is misbehaving badly... kill it */
12456             if( count == -2 ) {
12457                 DestroyChildProcess( cps->pr, 9 );
12458                 cps->pr = NoProc;
12459             }
12460
12461             DisplayFatalError(buf, error, 1);
12462         }
12463         return;
12464     }
12465     
12466     if ((end_str = strchr(message, '\r')) != NULL)
12467       *end_str = NULLCHAR;
12468     if ((end_str = strchr(message, '\n')) != NULL)
12469       *end_str = NULLCHAR;
12470     
12471     if (appData.debugMode) {
12472         TimeMark now; int print = 1;
12473         char *quote = ""; char c; int i;
12474
12475         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12476                 char start = message[0];
12477                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12478                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12479                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12480                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12481                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12482                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12483                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12484                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12485                         { quote = "# "; print = (appData.engineComments == 2); }
12486                 message[0] = start; // restore original message
12487         }
12488         if(print) {
12489                 GetTimeMark(&now);
12490                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12491                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12492                         quote,
12493                         message);
12494         }
12495     }
12496
12497     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12498     if (appData.icsEngineAnalyze) {
12499         if (strstr(message, "whisper") != NULL ||
12500              strstr(message, "kibitz") != NULL || 
12501             strstr(message, "tellics") != NULL) return;
12502     }
12503
12504     HandleMachineMove(message, cps);
12505 }
12506
12507
12508 void
12509 SendTimeControl(cps, mps, tc, inc, sd, st)
12510      ChessProgramState *cps;
12511      int mps, inc, sd, st;
12512      long tc;
12513 {
12514     char buf[MSG_SIZ];
12515     int seconds;
12516
12517     if( timeControl_2 > 0 ) {
12518         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12519             tc = timeControl_2;
12520         }
12521     }
12522     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12523     inc /= cps->timeOdds;
12524     st  /= cps->timeOdds;
12525
12526     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12527
12528     if (st > 0) {
12529       /* Set exact time per move, normally using st command */
12530       if (cps->stKludge) {
12531         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12532         seconds = st % 60;
12533         if (seconds == 0) {
12534           sprintf(buf, "level 1 %d\n", st/60);
12535         } else {
12536           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12537         }
12538       } else {
12539         sprintf(buf, "st %d\n", st);
12540       }
12541     } else {
12542       /* Set conventional or incremental time control, using level command */
12543       if (seconds == 0) {
12544         /* Note old gnuchess bug -- minutes:seconds used to not work.
12545            Fixed in later versions, but still avoid :seconds
12546            when seconds is 0. */
12547         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12548       } else {
12549         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12550                 seconds, inc/1000);
12551       }
12552     }
12553     SendToProgram(buf, cps);
12554
12555     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12556     /* Orthogonally, limit search to given depth */
12557     if (sd > 0) {
12558       if (cps->sdKludge) {
12559         sprintf(buf, "depth\n%d\n", sd);
12560       } else {
12561         sprintf(buf, "sd %d\n", sd);
12562       }
12563       SendToProgram(buf, cps);
12564     }
12565
12566     if(cps->nps > 0) { /* [HGM] nps */
12567         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12568         else {
12569                 sprintf(buf, "nps %d\n", cps->nps);
12570               SendToProgram(buf, cps);
12571         }
12572     }
12573 }
12574
12575 ChessProgramState *WhitePlayer()
12576 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12577 {
12578     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12579        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12580         return &second;
12581     return &first;
12582 }
12583
12584 void
12585 SendTimeRemaining(cps, machineWhite)
12586      ChessProgramState *cps;
12587      int /*boolean*/ machineWhite;
12588 {
12589     char message[MSG_SIZ];
12590     long time, otime;
12591
12592     /* Note: this routine must be called when the clocks are stopped
12593        or when they have *just* been set or switched; otherwise
12594        it will be off by the time since the current tick started.
12595     */
12596     if (machineWhite) {
12597         time = whiteTimeRemaining / 10;
12598         otime = blackTimeRemaining / 10;
12599     } else {
12600         time = blackTimeRemaining / 10;
12601         otime = whiteTimeRemaining / 10;
12602     }
12603     /* [HGM] translate opponent's time by time-odds factor */
12604     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12605     if (appData.debugMode) {
12606         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12607     }
12608
12609     if (time <= 0) time = 1;
12610     if (otime <= 0) otime = 1;
12611     
12612     sprintf(message, "time %ld\n", time);
12613     SendToProgram(message, cps);
12614
12615     sprintf(message, "otim %ld\n", otime);
12616     SendToProgram(message, cps);
12617 }
12618
12619 int
12620 BoolFeature(p, name, loc, cps)
12621      char **p;
12622      char *name;
12623      int *loc;
12624      ChessProgramState *cps;
12625 {
12626   char buf[MSG_SIZ];
12627   int len = strlen(name);
12628   int val;
12629   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12630     (*p) += len + 1;
12631     sscanf(*p, "%d", &val);
12632     *loc = (val != 0);
12633     while (**p && **p != ' ') (*p)++;
12634     sprintf(buf, "accepted %s\n", name);
12635     SendToProgram(buf, cps);
12636     return TRUE;
12637   }
12638   return FALSE;
12639 }
12640
12641 int
12642 IntFeature(p, name, loc, cps)
12643      char **p;
12644      char *name;
12645      int *loc;
12646      ChessProgramState *cps;
12647 {
12648   char buf[MSG_SIZ];
12649   int len = strlen(name);
12650   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12651     (*p) += len + 1;
12652     sscanf(*p, "%d", loc);
12653     while (**p && **p != ' ') (*p)++;
12654     sprintf(buf, "accepted %s\n", name);
12655     SendToProgram(buf, cps);
12656     return TRUE;
12657   }
12658   return FALSE;
12659 }
12660
12661 int
12662 StringFeature(p, name, loc, cps)
12663      char **p;
12664      char *name;
12665      char loc[];
12666      ChessProgramState *cps;
12667 {
12668   char buf[MSG_SIZ];
12669   int len = strlen(name);
12670   if (strncmp((*p), name, len) == 0
12671       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12672     (*p) += len + 2;
12673     sscanf(*p, "%[^\"]", loc);
12674     while (**p && **p != '\"') (*p)++;
12675     if (**p == '\"') (*p)++;
12676     sprintf(buf, "accepted %s\n", name);
12677     SendToProgram(buf, cps);
12678     return TRUE;
12679   }
12680   return FALSE;
12681 }
12682
12683 int 
12684 ParseOption(Option *opt, ChessProgramState *cps)
12685 // [HGM] options: process the string that defines an engine option, and determine
12686 // name, type, default value, and allowed value range
12687 {
12688         char *p, *q, buf[MSG_SIZ];
12689         int n, min = (-1)<<31, max = 1<<31, def;
12690
12691         if(p = strstr(opt->name, " -spin ")) {
12692             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12693             if(max < min) max = min; // enforce consistency
12694             if(def < min) def = min;
12695             if(def > max) def = max;
12696             opt->value = def;
12697             opt->min = min;
12698             opt->max = max;
12699             opt->type = Spin;
12700         } else if((p = strstr(opt->name, " -slider "))) {
12701             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12702             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12703             if(max < min) max = min; // enforce consistency
12704             if(def < min) def = min;
12705             if(def > max) def = max;
12706             opt->value = def;
12707             opt->min = min;
12708             opt->max = max;
12709             opt->type = Spin; // Slider;
12710         } else if((p = strstr(opt->name, " -string "))) {
12711             opt->textValue = p+9;
12712             opt->type = TextBox;
12713         } else if((p = strstr(opt->name, " -file "))) {
12714             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12715             opt->textValue = p+7;
12716             opt->type = TextBox; // FileName;
12717         } else if((p = strstr(opt->name, " -path "))) {
12718             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12719             opt->textValue = p+7;
12720             opt->type = TextBox; // PathName;
12721         } else if(p = strstr(opt->name, " -check ")) {
12722             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12723             opt->value = (def != 0);
12724             opt->type = CheckBox;
12725         } else if(p = strstr(opt->name, " -combo ")) {
12726             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12727             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12728             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12729             opt->value = n = 0;
12730             while(q = StrStr(q, " /// ")) {
12731                 n++; *q = 0;    // count choices, and null-terminate each of them
12732                 q += 5;
12733                 if(*q == '*') { // remember default, which is marked with * prefix
12734                     q++;
12735                     opt->value = n;
12736                 }
12737                 cps->comboList[cps->comboCnt++] = q;
12738             }
12739             cps->comboList[cps->comboCnt++] = NULL;
12740             opt->max = n + 1;
12741             opt->type = ComboBox;
12742         } else if(p = strstr(opt->name, " -button")) {
12743             opt->type = Button;
12744         } else if(p = strstr(opt->name, " -save")) {
12745             opt->type = SaveButton;
12746         } else return FALSE;
12747         *p = 0; // terminate option name
12748         // now look if the command-line options define a setting for this engine option.
12749         if(cps->optionSettings && cps->optionSettings[0])
12750             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12751         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12752                 sprintf(buf, "option %s", p);
12753                 if(p = strstr(buf, ",")) *p = 0;
12754                 strcat(buf, "\n");
12755                 SendToProgram(buf, cps);
12756         }
12757         return TRUE;
12758 }
12759
12760 void
12761 FeatureDone(cps, val)
12762      ChessProgramState* cps;
12763      int val;
12764 {
12765   DelayedEventCallback cb = GetDelayedEvent();
12766   if ((cb == InitBackEnd3 && cps == &first) ||
12767       (cb == TwoMachinesEventIfReady && cps == &second)) {
12768     CancelDelayedEvent();
12769     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12770   }
12771   cps->initDone = val;
12772 }
12773
12774 /* Parse feature command from engine */
12775 void
12776 ParseFeatures(args, cps)
12777      char* args;
12778      ChessProgramState *cps;  
12779 {
12780   char *p = args;
12781   char *q;
12782   int val;
12783   char buf[MSG_SIZ];
12784
12785   for (;;) {
12786     while (*p == ' ') p++;
12787     if (*p == NULLCHAR) return;
12788
12789     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12790     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12791     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12792     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12793     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12794     if (BoolFeature(&p, "reuse", &val, cps)) {
12795       /* Engine can disable reuse, but can't enable it if user said no */
12796       if (!val) cps->reuse = FALSE;
12797       continue;
12798     }
12799     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12800     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12801       if (gameMode == TwoMachinesPlay) {
12802         DisplayTwoMachinesTitle();
12803       } else {
12804         DisplayTitle("");
12805       }
12806       continue;
12807     }
12808     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12809     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12810     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12811     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12812     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12813     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12814     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12815     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12816     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12817     if (IntFeature(&p, "done", &val, cps)) {
12818       FeatureDone(cps, val);
12819       continue;
12820     }
12821     /* Added by Tord: */
12822     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12823     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12824     /* End of additions by Tord */
12825
12826     /* [HGM] added features: */
12827     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12828     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12829     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12830     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12831     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12832     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12833     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12834         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12835             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12836             SendToProgram(buf, cps);
12837             continue;
12838         }
12839         if(cps->nrOptions >= MAX_OPTIONS) {
12840             cps->nrOptions--;
12841             sprintf(buf, "%s engine has too many options\n", cps->which);
12842             DisplayError(buf, 0);
12843         }
12844         continue;
12845     }
12846     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12847     /* End of additions by HGM */
12848
12849     /* unknown feature: complain and skip */
12850     q = p;
12851     while (*q && *q != '=') q++;
12852     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12853     SendToProgram(buf, cps);
12854     p = q;
12855     if (*p == '=') {
12856       p++;
12857       if (*p == '\"') {
12858         p++;
12859         while (*p && *p != '\"') p++;
12860         if (*p == '\"') p++;
12861       } else {
12862         while (*p && *p != ' ') p++;
12863       }
12864     }
12865   }
12866
12867 }
12868
12869 void
12870 PeriodicUpdatesEvent(newState)
12871      int newState;
12872 {
12873     if (newState == appData.periodicUpdates)
12874       return;
12875
12876     appData.periodicUpdates=newState;
12877
12878     /* Display type changes, so update it now */
12879 //    DisplayAnalysis();
12880
12881     /* Get the ball rolling again... */
12882     if (newState) {
12883         AnalysisPeriodicEvent(1);
12884         StartAnalysisClock();
12885     }
12886 }
12887
12888 void
12889 PonderNextMoveEvent(newState)
12890      int newState;
12891 {
12892     if (newState == appData.ponderNextMove) return;
12893     if (gameMode == EditPosition) EditPositionDone();
12894     if (newState) {
12895         SendToProgram("hard\n", &first);
12896         if (gameMode == TwoMachinesPlay) {
12897             SendToProgram("hard\n", &second);
12898         }
12899     } else {
12900         SendToProgram("easy\n", &first);
12901         thinkOutput[0] = NULLCHAR;
12902         if (gameMode == TwoMachinesPlay) {
12903             SendToProgram("easy\n", &second);
12904         }
12905     }
12906     appData.ponderNextMove = newState;
12907 }
12908
12909 void
12910 NewSettingEvent(option, command, value)
12911      char *command;
12912      int option, value;
12913 {
12914     char buf[MSG_SIZ];
12915
12916     if (gameMode == EditPosition) EditPositionDone();
12917     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12918     SendToProgram(buf, &first);
12919     if (gameMode == TwoMachinesPlay) {
12920         SendToProgram(buf, &second);
12921     }
12922 }
12923
12924 void
12925 ShowThinkingEvent()
12926 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12927 {
12928     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12929     int newState = appData.showThinking
12930         // [HGM] thinking: other features now need thinking output as well
12931         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12932     
12933     if (oldState == newState) return;
12934     oldState = newState;
12935     if (gameMode == EditPosition) EditPositionDone();
12936     if (oldState) {
12937         SendToProgram("post\n", &first);
12938         if (gameMode == TwoMachinesPlay) {
12939             SendToProgram("post\n", &second);
12940         }
12941     } else {
12942         SendToProgram("nopost\n", &first);
12943         thinkOutput[0] = NULLCHAR;
12944         if (gameMode == TwoMachinesPlay) {
12945             SendToProgram("nopost\n", &second);
12946         }
12947     }
12948 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12949 }
12950
12951 void
12952 AskQuestionEvent(title, question, replyPrefix, which)
12953      char *title; char *question; char *replyPrefix; char *which;
12954 {
12955   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12956   if (pr == NoProc) return;
12957   AskQuestion(title, question, replyPrefix, pr);
12958 }
12959
12960 void
12961 DisplayMove(moveNumber)
12962      int moveNumber;
12963 {
12964     char message[MSG_SIZ];
12965     char res[MSG_SIZ];
12966     char cpThinkOutput[MSG_SIZ];
12967
12968     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12969     
12970     if (moveNumber == forwardMostMove - 1 || 
12971         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12972
12973         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12974
12975         if (strchr(cpThinkOutput, '\n')) {
12976             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12977         }
12978     } else {
12979         *cpThinkOutput = NULLCHAR;
12980     }
12981
12982     /* [AS] Hide thinking from human user */
12983     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12984         *cpThinkOutput = NULLCHAR;
12985         if( thinkOutput[0] != NULLCHAR ) {
12986             int i;
12987
12988             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12989                 cpThinkOutput[i] = '.';
12990             }
12991             cpThinkOutput[i] = NULLCHAR;
12992             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12993         }
12994     }
12995
12996     if (moveNumber == forwardMostMove - 1 &&
12997         gameInfo.resultDetails != NULL) {
12998         if (gameInfo.resultDetails[0] == NULLCHAR) {
12999             sprintf(res, " %s", PGNResult(gameInfo.result));
13000         } else {
13001             sprintf(res, " {%s} %s",
13002                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13003         }
13004     } else {
13005         res[0] = NULLCHAR;
13006     }
13007
13008     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13009         DisplayMessage(res, cpThinkOutput);
13010     } else {
13011         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13012                 WhiteOnMove(moveNumber) ? " " : ".. ",
13013                 parseList[moveNumber], res);
13014         DisplayMessage(message, cpThinkOutput);
13015     }
13016 }
13017
13018 void
13019 DisplayComment(moveNumber, text)
13020      int moveNumber;
13021      char *text;
13022 {
13023     char title[MSG_SIZ];
13024     char buf[8000]; // comment can be long!
13025     int score, depth;
13026     
13027     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13028       strcpy(title, "Comment");
13029     } else {
13030       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13031               WhiteOnMove(moveNumber) ? " " : ".. ",
13032               parseList[moveNumber]);
13033     }
13034     // [HGM] PV info: display PV info together with (or as) comment
13035     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13036       if(text == NULL) text = "";                                           
13037       score = pvInfoList[moveNumber].score;
13038       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13039               depth, (pvInfoList[moveNumber].time+50)/100, text);
13040       text = buf;
13041     }
13042     if (text != NULL && (appData.autoDisplayComment || commentUp))
13043         CommentPopUp(title, text);
13044 }
13045
13046 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13047  * might be busy thinking or pondering.  It can be omitted if your
13048  * gnuchess is configured to stop thinking immediately on any user
13049  * input.  However, that gnuchess feature depends on the FIONREAD
13050  * ioctl, which does not work properly on some flavors of Unix.
13051  */
13052 void
13053 Attention(cps)
13054      ChessProgramState *cps;
13055 {
13056 #if ATTENTION
13057     if (!cps->useSigint) return;
13058     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13059     switch (gameMode) {
13060       case MachinePlaysWhite:
13061       case MachinePlaysBlack:
13062       case TwoMachinesPlay:
13063       case IcsPlayingWhite:
13064       case IcsPlayingBlack:
13065       case AnalyzeMode:
13066       case AnalyzeFile:
13067         /* Skip if we know it isn't thinking */
13068         if (!cps->maybeThinking) return;
13069         if (appData.debugMode)
13070           fprintf(debugFP, "Interrupting %s\n", cps->which);
13071         InterruptChildProcess(cps->pr);
13072         cps->maybeThinking = FALSE;
13073         break;
13074       default:
13075         break;
13076     }
13077 #endif /*ATTENTION*/
13078 }
13079
13080 int
13081 CheckFlags()
13082 {
13083     if (whiteTimeRemaining <= 0) {
13084         if (!whiteFlag) {
13085             whiteFlag = TRUE;
13086             if (appData.icsActive) {
13087                 if (appData.autoCallFlag &&
13088                     gameMode == IcsPlayingBlack && !blackFlag) {
13089                   SendToICS(ics_prefix);
13090                   SendToICS("flag\n");
13091                 }
13092             } else {
13093                 if (blackFlag) {
13094                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13095                 } else {
13096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13097                     if (appData.autoCallFlag) {
13098                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13099                         return TRUE;
13100                     }
13101                 }
13102             }
13103         }
13104     }
13105     if (blackTimeRemaining <= 0) {
13106         if (!blackFlag) {
13107             blackFlag = TRUE;
13108             if (appData.icsActive) {
13109                 if (appData.autoCallFlag &&
13110                     gameMode == IcsPlayingWhite && !whiteFlag) {
13111                   SendToICS(ics_prefix);
13112                   SendToICS("flag\n");
13113                 }
13114             } else {
13115                 if (whiteFlag) {
13116                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13117                 } else {
13118                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13119                     if (appData.autoCallFlag) {
13120                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13121                         return TRUE;
13122                     }
13123                 }
13124             }
13125         }
13126     }
13127     return FALSE;
13128 }
13129
13130 void
13131 CheckTimeControl()
13132 {
13133     if (!appData.clockMode || appData.icsActive ||
13134         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13135
13136     /*
13137      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13138      */
13139     if ( !WhiteOnMove(forwardMostMove) )
13140         /* White made time control */
13141         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13142         /* [HGM] time odds: correct new time quota for time odds! */
13143                                             / WhitePlayer()->timeOdds;
13144       else
13145         /* Black made time control */
13146         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13147                                             / WhitePlayer()->other->timeOdds;
13148 }
13149
13150 void
13151 DisplayBothClocks()
13152 {
13153     int wom = gameMode == EditPosition ?
13154       !blackPlaysFirst : WhiteOnMove(currentMove);
13155     DisplayWhiteClock(whiteTimeRemaining, wom);
13156     DisplayBlackClock(blackTimeRemaining, !wom);
13157 }
13158
13159
13160 /* Timekeeping seems to be a portability nightmare.  I think everyone
13161    has ftime(), but I'm really not sure, so I'm including some ifdefs
13162    to use other calls if you don't.  Clocks will be less accurate if
13163    you have neither ftime nor gettimeofday.
13164 */
13165
13166 /* VS 2008 requires the #include outside of the function */
13167 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13168 #include <sys/timeb.h>
13169 #endif
13170
13171 /* Get the current time as a TimeMark */
13172 void
13173 GetTimeMark(tm)
13174      TimeMark *tm;
13175 {
13176 #if HAVE_GETTIMEOFDAY
13177
13178     struct timeval timeVal;
13179     struct timezone timeZone;
13180
13181     gettimeofday(&timeVal, &timeZone);
13182     tm->sec = (long) timeVal.tv_sec; 
13183     tm->ms = (int) (timeVal.tv_usec / 1000L);
13184
13185 #else /*!HAVE_GETTIMEOFDAY*/
13186 #if HAVE_FTIME
13187
13188 // include <sys/timeb.h> / moved to just above start of function
13189     struct timeb timeB;
13190
13191     ftime(&timeB);
13192     tm->sec = (long) timeB.time;
13193     tm->ms = (int) timeB.millitm;
13194
13195 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13196     tm->sec = (long) time(NULL);
13197     tm->ms = 0;
13198 #endif
13199 #endif
13200 }
13201
13202 /* Return the difference in milliseconds between two
13203    time marks.  We assume the difference will fit in a long!
13204 */
13205 long
13206 SubtractTimeMarks(tm2, tm1)
13207      TimeMark *tm2, *tm1;
13208 {
13209     return 1000L*(tm2->sec - tm1->sec) +
13210            (long) (tm2->ms - tm1->ms);
13211 }
13212
13213
13214 /*
13215  * Code to manage the game clocks.
13216  *
13217  * In tournament play, black starts the clock and then white makes a move.
13218  * We give the human user a slight advantage if he is playing white---the
13219  * clocks don't run until he makes his first move, so it takes zero time.
13220  * Also, we don't account for network lag, so we could get out of sync
13221  * with GNU Chess's clock -- but then, referees are always right.  
13222  */
13223
13224 static TimeMark tickStartTM;
13225 static long intendedTickLength;
13226
13227 long
13228 NextTickLength(timeRemaining)
13229      long timeRemaining;
13230 {
13231     long nominalTickLength, nextTickLength;
13232
13233     if (timeRemaining > 0L && timeRemaining <= 10000L)
13234       nominalTickLength = 100L;
13235     else
13236       nominalTickLength = 1000L;
13237     nextTickLength = timeRemaining % nominalTickLength;
13238     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13239
13240     return nextTickLength;
13241 }
13242
13243 /* Adjust clock one minute up or down */
13244 void
13245 AdjustClock(Boolean which, int dir)
13246 {
13247     if(which) blackTimeRemaining += 60000*dir;
13248     else      whiteTimeRemaining += 60000*dir;
13249     DisplayBothClocks();
13250 }
13251
13252 /* Stop clocks and reset to a fresh time control */
13253 void
13254 ResetClocks() 
13255 {
13256     (void) StopClockTimer();
13257     if (appData.icsActive) {
13258         whiteTimeRemaining = blackTimeRemaining = 0;
13259     } else { /* [HGM] correct new time quote for time odds */
13260         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13261         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13262     }
13263     if (whiteFlag || blackFlag) {
13264         DisplayTitle("");
13265         whiteFlag = blackFlag = FALSE;
13266     }
13267     DisplayBothClocks();
13268 }
13269
13270 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13271
13272 /* Decrement running clock by amount of time that has passed */
13273 void
13274 DecrementClocks()
13275 {
13276     long timeRemaining;
13277     long lastTickLength, fudge;
13278     TimeMark now;
13279
13280     if (!appData.clockMode) return;
13281     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13282         
13283     GetTimeMark(&now);
13284
13285     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13286
13287     /* Fudge if we woke up a little too soon */
13288     fudge = intendedTickLength - lastTickLength;
13289     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13290
13291     if (WhiteOnMove(forwardMostMove)) {
13292         if(whiteNPS >= 0) lastTickLength = 0;
13293         timeRemaining = whiteTimeRemaining -= lastTickLength;
13294         DisplayWhiteClock(whiteTimeRemaining - fudge,
13295                           WhiteOnMove(currentMove));
13296     } else {
13297         if(blackNPS >= 0) lastTickLength = 0;
13298         timeRemaining = blackTimeRemaining -= lastTickLength;
13299         DisplayBlackClock(blackTimeRemaining - fudge,
13300                           !WhiteOnMove(currentMove));
13301     }
13302
13303     if (CheckFlags()) return;
13304         
13305     tickStartTM = now;
13306     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13307     StartClockTimer(intendedTickLength);
13308
13309     /* if the time remaining has fallen below the alarm threshold, sound the
13310      * alarm. if the alarm has sounded and (due to a takeback or time control
13311      * with increment) the time remaining has increased to a level above the
13312      * threshold, reset the alarm so it can sound again. 
13313      */
13314     
13315     if (appData.icsActive && appData.icsAlarm) {
13316
13317         /* make sure we are dealing with the user's clock */
13318         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13319                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13320            )) return;
13321
13322         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13323             alarmSounded = FALSE;
13324         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13325             PlayAlarmSound();
13326             alarmSounded = TRUE;
13327         }
13328     }
13329 }
13330
13331
13332 /* A player has just moved, so stop the previously running
13333    clock and (if in clock mode) start the other one.
13334    We redisplay both clocks in case we're in ICS mode, because
13335    ICS gives us an update to both clocks after every move.
13336    Note that this routine is called *after* forwardMostMove
13337    is updated, so the last fractional tick must be subtracted
13338    from the color that is *not* on move now.
13339 */
13340 void
13341 SwitchClocks()
13342 {
13343     long lastTickLength;
13344     TimeMark now;
13345     int flagged = FALSE;
13346
13347     GetTimeMark(&now);
13348
13349     if (StopClockTimer() && appData.clockMode) {
13350         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13351         if (WhiteOnMove(forwardMostMove)) {
13352             if(blackNPS >= 0) lastTickLength = 0;
13353             blackTimeRemaining -= lastTickLength;
13354            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13355 //         if(pvInfoList[forwardMostMove-1].time == -1)
13356                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13357                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13358         } else {
13359            if(whiteNPS >= 0) lastTickLength = 0;
13360            whiteTimeRemaining -= lastTickLength;
13361            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13362 //         if(pvInfoList[forwardMostMove-1].time == -1)
13363                  pvInfoList[forwardMostMove-1].time = 
13364                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13365         }
13366         flagged = CheckFlags();
13367     }
13368     CheckTimeControl();
13369
13370     if (flagged || !appData.clockMode) return;
13371
13372     switch (gameMode) {
13373       case MachinePlaysBlack:
13374       case MachinePlaysWhite:
13375       case BeginningOfGame:
13376         if (pausing) return;
13377         break;
13378
13379       case EditGame:
13380       case PlayFromGameFile:
13381       case IcsExamining:
13382         return;
13383
13384       default:
13385         break;
13386     }
13387
13388     tickStartTM = now;
13389     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13390       whiteTimeRemaining : blackTimeRemaining);
13391     StartClockTimer(intendedTickLength);
13392 }
13393         
13394
13395 /* Stop both clocks */
13396 void
13397 StopClocks()
13398 {       
13399     long lastTickLength;
13400     TimeMark now;
13401
13402     if (!StopClockTimer()) return;
13403     if (!appData.clockMode) return;
13404
13405     GetTimeMark(&now);
13406
13407     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13408     if (WhiteOnMove(forwardMostMove)) {
13409         if(whiteNPS >= 0) lastTickLength = 0;
13410         whiteTimeRemaining -= lastTickLength;
13411         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13412     } else {
13413         if(blackNPS >= 0) lastTickLength = 0;
13414         blackTimeRemaining -= lastTickLength;
13415         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13416     }
13417     CheckFlags();
13418 }
13419         
13420 /* Start clock of player on move.  Time may have been reset, so
13421    if clock is already running, stop and restart it. */
13422 void
13423 StartClocks()
13424 {
13425     (void) StopClockTimer(); /* in case it was running already */
13426     DisplayBothClocks();
13427     if (CheckFlags()) return;
13428
13429     if (!appData.clockMode) return;
13430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13431
13432     GetTimeMark(&tickStartTM);
13433     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13434       whiteTimeRemaining : blackTimeRemaining);
13435
13436    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13437     whiteNPS = blackNPS = -1; 
13438     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13439        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13440         whiteNPS = first.nps;
13441     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13442        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13443         blackNPS = first.nps;
13444     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13445         whiteNPS = second.nps;
13446     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13447         blackNPS = second.nps;
13448     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13449
13450     StartClockTimer(intendedTickLength);
13451 }
13452
13453 char *
13454 TimeString(ms)
13455      long ms;
13456 {
13457     long second, minute, hour, day;
13458     char *sign = "";
13459     static char buf[32];
13460     
13461     if (ms > 0 && ms <= 9900) {
13462       /* convert milliseconds to tenths, rounding up */
13463       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13464
13465       sprintf(buf, " %03.1f ", tenths/10.0);
13466       return buf;
13467     }
13468
13469     /* convert milliseconds to seconds, rounding up */
13470     /* use floating point to avoid strangeness of integer division
13471        with negative dividends on many machines */
13472     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13473
13474     if (second < 0) {
13475         sign = "-";
13476         second = -second;
13477     }
13478     
13479     day = second / (60 * 60 * 24);
13480     second = second % (60 * 60 * 24);
13481     hour = second / (60 * 60);
13482     second = second % (60 * 60);
13483     minute = second / 60;
13484     second = second % 60;
13485     
13486     if (day > 0)
13487       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13488               sign, day, hour, minute, second);
13489     else if (hour > 0)
13490       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13491     else
13492       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13493     
13494     return buf;
13495 }
13496
13497
13498 /*
13499  * This is necessary because some C libraries aren't ANSI C compliant yet.
13500  */
13501 char *
13502 StrStr(string, match)
13503      char *string, *match;
13504 {
13505     int i, length;
13506     
13507     length = strlen(match);
13508     
13509     for (i = strlen(string) - length; i >= 0; i--, string++)
13510       if (!strncmp(match, string, length))
13511         return string;
13512     
13513     return NULL;
13514 }
13515
13516 char *
13517 StrCaseStr(string, match)
13518      char *string, *match;
13519 {
13520     int i, j, length;
13521     
13522     length = strlen(match);
13523     
13524     for (i = strlen(string) - length; i >= 0; i--, string++) {
13525         for (j = 0; j < length; j++) {
13526             if (ToLower(match[j]) != ToLower(string[j]))
13527               break;
13528         }
13529         if (j == length) return string;
13530     }
13531
13532     return NULL;
13533 }
13534
13535 #ifndef _amigados
13536 int
13537 StrCaseCmp(s1, s2)
13538      char *s1, *s2;
13539 {
13540     char c1, c2;
13541     
13542     for (;;) {
13543         c1 = ToLower(*s1++);
13544         c2 = ToLower(*s2++);
13545         if (c1 > c2) return 1;
13546         if (c1 < c2) return -1;
13547         if (c1 == NULLCHAR) return 0;
13548     }
13549 }
13550
13551
13552 int
13553 ToLower(c)
13554      int c;
13555 {
13556     return isupper(c) ? tolower(c) : c;
13557 }
13558
13559
13560 int
13561 ToUpper(c)
13562      int c;
13563 {
13564     return islower(c) ? toupper(c) : c;
13565 }
13566 #endif /* !_amigados    */
13567
13568 char *
13569 StrSave(s)
13570      char *s;
13571 {
13572     char *ret;
13573
13574     if ((ret = (char *) malloc(strlen(s) + 1))) {
13575         strcpy(ret, s);
13576     }
13577     return ret;
13578 }
13579
13580 char *
13581 StrSavePtr(s, savePtr)
13582      char *s, **savePtr;
13583 {
13584     if (*savePtr) {
13585         free(*savePtr);
13586     }
13587     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13588         strcpy(*savePtr, s);
13589     }
13590     return(*savePtr);
13591 }
13592
13593 char *
13594 PGNDate()
13595 {
13596     time_t clock;
13597     struct tm *tm;
13598     char buf[MSG_SIZ];
13599
13600     clock = time((time_t *)NULL);
13601     tm = localtime(&clock);
13602     sprintf(buf, "%04d.%02d.%02d",
13603             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13604     return StrSave(buf);
13605 }
13606
13607
13608 char *
13609 PositionToFEN(move, overrideCastling)
13610      int move;
13611      char *overrideCastling;
13612 {
13613     int i, j, fromX, fromY, toX, toY;
13614     int whiteToPlay;
13615     char buf[128];
13616     char *p, *q;
13617     int emptycount;
13618     ChessSquare piece;
13619
13620     whiteToPlay = (gameMode == EditPosition) ?
13621       !blackPlaysFirst : (move % 2 == 0);
13622     p = buf;
13623
13624     /* Piece placement data */
13625     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13626         emptycount = 0;
13627         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13628             if (boards[move][i][j] == EmptySquare) {
13629                 emptycount++;
13630             } else { ChessSquare piece = boards[move][i][j];
13631                 if (emptycount > 0) {
13632                     if(emptycount<10) /* [HGM] can be >= 10 */
13633                         *p++ = '0' + emptycount;
13634                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13635                     emptycount = 0;
13636                 }
13637                 if(PieceToChar(piece) == '+') {
13638                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13639                     *p++ = '+';
13640                     piece = (ChessSquare)(DEMOTED piece);
13641                 } 
13642                 *p++ = PieceToChar(piece);
13643                 if(p[-1] == '~') {
13644                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13645                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13646                     *p++ = '~';
13647                 }
13648             }
13649         }
13650         if (emptycount > 0) {
13651             if(emptycount<10) /* [HGM] can be >= 10 */
13652                 *p++ = '0' + emptycount;
13653             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13654             emptycount = 0;
13655         }
13656         *p++ = '/';
13657     }
13658     *(p - 1) = ' ';
13659
13660     /* [HGM] print Crazyhouse or Shogi holdings */
13661     if( gameInfo.holdingsWidth ) {
13662         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13663         q = p;
13664         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13665             piece = boards[move][i][BOARD_WIDTH-1];
13666             if( piece != EmptySquare )
13667               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13668                   *p++ = PieceToChar(piece);
13669         }
13670         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13671             piece = boards[move][BOARD_HEIGHT-i-1][0];
13672             if( piece != EmptySquare )
13673               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13674                   *p++ = PieceToChar(piece);
13675         }
13676
13677         if( q == p ) *p++ = '-';
13678         *p++ = ']';
13679         *p++ = ' ';
13680     }
13681
13682     /* Active color */
13683     *p++ = whiteToPlay ? 'w' : 'b';
13684     *p++ = ' ';
13685
13686   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13687     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13688   } else {
13689   if(nrCastlingRights) {
13690      q = p;
13691      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13692        /* [HGM] write directly from rights */
13693            if(castlingRights[move][2] >= 0 &&
13694               castlingRights[move][0] >= 0   )
13695                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13696            if(castlingRights[move][2] >= 0 &&
13697               castlingRights[move][1] >= 0   )
13698                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13699            if(castlingRights[move][5] >= 0 &&
13700               castlingRights[move][3] >= 0   )
13701                 *p++ = castlingRights[move][3] + AAA;
13702            if(castlingRights[move][5] >= 0 &&
13703               castlingRights[move][4] >= 0   )
13704                 *p++ = castlingRights[move][4] + AAA;
13705      } else {
13706
13707         /* [HGM] write true castling rights */
13708         if( nrCastlingRights == 6 ) {
13709             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13710                castlingRights[move][2] >= 0  ) *p++ = 'K';
13711             if(castlingRights[move][1] == BOARD_LEFT &&
13712                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13713             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13714                castlingRights[move][5] >= 0  ) *p++ = 'k';
13715             if(castlingRights[move][4] == BOARD_LEFT &&
13716                castlingRights[move][5] >= 0  ) *p++ = 'q';
13717         }
13718      }
13719      if (q == p) *p++ = '-'; /* No castling rights */
13720      *p++ = ' ';
13721   }
13722
13723   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13724      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13725     /* En passant target square */
13726     if (move > backwardMostMove) {
13727         fromX = moveList[move - 1][0] - AAA;
13728         fromY = moveList[move - 1][1] - ONE;
13729         toX = moveList[move - 1][2] - AAA;
13730         toY = moveList[move - 1][3] - ONE;
13731         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13732             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13733             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13734             fromX == toX) {
13735             /* 2-square pawn move just happened */
13736             *p++ = toX + AAA;
13737             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13738         } else {
13739             *p++ = '-';
13740         }
13741     } else if(move == backwardMostMove) {
13742         // [HGM] perhaps we should always do it like this, and forget the above?
13743         if(epStatus[move] >= 0) {
13744             *p++ = epStatus[move] + AAA;
13745             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13746         } else {
13747             *p++ = '-';
13748         }
13749     } else {
13750         *p++ = '-';
13751     }
13752     *p++ = ' ';
13753   }
13754   }
13755
13756     /* [HGM] find reversible plies */
13757     {   int i = 0, j=move;
13758
13759         if (appData.debugMode) { int k;
13760             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13761             for(k=backwardMostMove; k<=forwardMostMove; k++)
13762                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13763
13764         }
13765
13766         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13767         if( j == backwardMostMove ) i += initialRulePlies;
13768         sprintf(p, "%d ", i);
13769         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13770     }
13771     /* Fullmove number */
13772     sprintf(p, "%d", (move / 2) + 1);
13773     
13774     return StrSave(buf);
13775 }
13776
13777 Boolean
13778 ParseFEN(board, blackPlaysFirst, fen)
13779     Board board;
13780      int *blackPlaysFirst;
13781      char *fen;
13782 {
13783     int i, j;
13784     char *p;
13785     int emptycount;
13786     ChessSquare piece;
13787
13788     p = fen;
13789
13790     /* [HGM] by default clear Crazyhouse holdings, if present */
13791     if(gameInfo.holdingsWidth) {
13792        for(i=0; i<BOARD_HEIGHT; i++) {
13793            board[i][0]             = EmptySquare; /* black holdings */
13794            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13795            board[i][1]             = (ChessSquare) 0; /* black counts */
13796            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13797        }
13798     }
13799
13800     /* Piece placement data */
13801     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13802         j = 0;
13803         for (;;) {
13804             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13805                 if (*p == '/') p++;
13806                 emptycount = gameInfo.boardWidth - j;
13807                 while (emptycount--)
13808                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13809                 break;
13810 #if(BOARD_SIZE >= 10)
13811             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13812                 p++; emptycount=10;
13813                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13814                 while (emptycount--)
13815                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13816 #endif
13817             } else if (isdigit(*p)) {
13818                 emptycount = *p++ - '0';
13819                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13820                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13821                 while (emptycount--)
13822                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13823             } else if (*p == '+' || isalpha(*p)) {
13824                 if (j >= gameInfo.boardWidth) return FALSE;
13825                 if(*p=='+') {
13826                     piece = CharToPiece(*++p);
13827                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13828                     piece = (ChessSquare) (PROMOTED piece ); p++;
13829                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13830                 } else piece = CharToPiece(*p++);
13831
13832                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13833                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13834                     piece = (ChessSquare) (PROMOTED piece);
13835                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13836                     p++;
13837                 }
13838                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13839             } else {
13840                 return FALSE;
13841             }
13842         }
13843     }
13844     while (*p == '/' || *p == ' ') p++;
13845
13846     /* [HGM] look for Crazyhouse holdings here */
13847     while(*p==' ') p++;
13848     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13849         if(*p == '[') p++;
13850         if(*p == '-' ) *p++; /* empty holdings */ else {
13851             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13852             /* if we would allow FEN reading to set board size, we would   */
13853             /* have to add holdings and shift the board read so far here   */
13854             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13855                 *p++;
13856                 if((int) piece >= (int) BlackPawn ) {
13857                     i = (int)piece - (int)BlackPawn;
13858                     i = PieceToNumber((ChessSquare)i);
13859                     if( i >= gameInfo.holdingsSize ) return FALSE;
13860                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13861                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13862                 } else {
13863                     i = (int)piece - (int)WhitePawn;
13864                     i = PieceToNumber((ChessSquare)i);
13865                     if( i >= gameInfo.holdingsSize ) return FALSE;
13866                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13867                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13868                 }
13869             }
13870         }
13871         if(*p == ']') *p++;
13872     }
13873
13874     while(*p == ' ') p++;
13875
13876     /* Active color */
13877     switch (*p++) {
13878       case 'w':
13879         *blackPlaysFirst = FALSE;
13880         break;
13881       case 'b': 
13882         *blackPlaysFirst = TRUE;
13883         break;
13884       default:
13885         return FALSE;
13886     }
13887
13888     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13889     /* return the extra info in global variiables             */
13890
13891     /* set defaults in case FEN is incomplete */
13892     FENepStatus = EP_UNKNOWN;
13893     for(i=0; i<nrCastlingRights; i++ ) {
13894         FENcastlingRights[i] =
13895             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13896     }   /* assume possible unless obviously impossible */
13897     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13898     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13899     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13900     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13901     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13902     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13903     FENrulePlies = 0;
13904
13905     while(*p==' ') p++;
13906     if(nrCastlingRights) {
13907       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13908           /* castling indicator present, so default becomes no castlings */
13909           for(i=0; i<nrCastlingRights; i++ ) {
13910                  FENcastlingRights[i] = -1;
13911           }
13912       }
13913       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13914              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13915              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13916              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13917         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13918
13919         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13920             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13921             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13922         }
13923         switch(c) {
13924           case'K':
13925               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13926               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13927               FENcastlingRights[2] = whiteKingFile;
13928               break;
13929           case'Q':
13930               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13931               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13932               FENcastlingRights[2] = whiteKingFile;
13933               break;
13934           case'k':
13935               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13936               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13937               FENcastlingRights[5] = blackKingFile;
13938               break;
13939           case'q':
13940               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13941               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13942               FENcastlingRights[5] = blackKingFile;
13943           case '-':
13944               break;
13945           default: /* FRC castlings */
13946               if(c >= 'a') { /* black rights */
13947                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13948                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13949                   if(i == BOARD_RGHT) break;
13950                   FENcastlingRights[5] = i;
13951                   c -= AAA;
13952                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13953                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13954                   if(c > i)
13955                       FENcastlingRights[3] = c;
13956                   else
13957                       FENcastlingRights[4] = c;
13958               } else { /* white rights */
13959                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13960                     if(board[0][i] == WhiteKing) break;
13961                   if(i == BOARD_RGHT) break;
13962                   FENcastlingRights[2] = i;
13963                   c -= AAA - 'a' + 'A';
13964                   if(board[0][c] >= WhiteKing) break;
13965                   if(c > i)
13966                       FENcastlingRights[0] = c;
13967                   else
13968                       FENcastlingRights[1] = c;
13969               }
13970         }
13971       }
13972     if (appData.debugMode) {
13973         fprintf(debugFP, "FEN castling rights:");
13974         for(i=0; i<nrCastlingRights; i++)
13975         fprintf(debugFP, " %d", FENcastlingRights[i]);
13976         fprintf(debugFP, "\n");
13977     }
13978
13979       while(*p==' ') p++;
13980     }
13981
13982     /* read e.p. field in games that know e.p. capture */
13983     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13984        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13985       if(*p=='-') {
13986         p++; FENepStatus = EP_NONE;
13987       } else {
13988          char c = *p++ - AAA;
13989
13990          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13991          if(*p >= '0' && *p <='9') *p++;
13992          FENepStatus = c;
13993       }
13994     }
13995
13996
13997     if(sscanf(p, "%d", &i) == 1) {
13998         FENrulePlies = i; /* 50-move ply counter */
13999         /* (The move number is still ignored)    */
14000     }
14001
14002     return TRUE;
14003 }
14004       
14005 void
14006 EditPositionPasteFEN(char *fen)
14007 {
14008   if (fen != NULL) {
14009     Board initial_position;
14010
14011     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14012       DisplayError(_("Bad FEN position in clipboard"), 0);
14013       return ;
14014     } else {
14015       int savedBlackPlaysFirst = blackPlaysFirst;
14016       EditPositionEvent();
14017       blackPlaysFirst = savedBlackPlaysFirst;
14018       CopyBoard(boards[0], initial_position);
14019           /* [HGM] copy FEN attributes as well */
14020           {   int i;
14021               initialRulePlies = FENrulePlies;
14022               epStatus[0] = FENepStatus;
14023               for( i=0; i<nrCastlingRights; i++ )
14024                   castlingRights[0][i] = FENcastlingRights[i];
14025           }
14026       EditPositionDone();
14027       DisplayBothClocks();
14028       DrawPosition(FALSE, boards[currentMove]);
14029     }
14030   }
14031 }
14032
14033 static char cseq[12] = "\\   ";
14034
14035 Boolean set_cont_sequence(char *new_seq)
14036 {
14037     int len;
14038     Boolean ret;
14039
14040     // handle bad attempts to set the sequence
14041         if (!new_seq)
14042                 return 0; // acceptable error - no debug
14043
14044     len = strlen(new_seq);
14045     ret = (len > 0) && (len < sizeof(cseq));
14046     if (ret)
14047         strcpy(cseq, new_seq);
14048     else if (appData.debugMode)
14049         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14050     return ret;
14051 }
14052
14053 /*
14054     reformat a source message so words don't cross the width boundary.  internal
14055     newlines are not removed.  returns the wrapped size (no null character unless
14056     included in source message).  If dest is NULL, only calculate the size required
14057     for the dest buffer.  lp argument indicats line position upon entry, and it's
14058     passed back upon exit.
14059 */
14060 int wrap(char *dest, char *src, int count, int width, int *lp)
14061 {
14062     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14063
14064     cseq_len = strlen(cseq);
14065     old_line = line = *lp;
14066     ansi = len = clen = 0;
14067
14068     for (i=0; i < count; i++)
14069     {
14070         if (src[i] == '\033')
14071             ansi = 1;
14072
14073         // if we hit the width, back up
14074         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14075         {
14076             // store i & len in case the word is too long
14077             old_i = i, old_len = len;
14078
14079             // find the end of the last word
14080             while (i && src[i] != ' ' && src[i] != '\n')
14081             {
14082                 i--;
14083                 len--;
14084             }
14085
14086             // word too long?  restore i & len before splitting it
14087             if ((old_i-i+clen) >= width)
14088             {
14089                 i = old_i;
14090                 len = old_len;
14091             }
14092
14093             // extra space?
14094             if (i && src[i-1] == ' ')
14095                 len--;
14096
14097             if (src[i] != ' ' && src[i] != '\n')
14098             {
14099                 i--;
14100                 if (len)
14101                     len--;
14102             }
14103
14104             // now append the newline and continuation sequence
14105             if (dest)
14106                 dest[len] = '\n';
14107             len++;
14108             if (dest)
14109                 strncpy(dest+len, cseq, cseq_len);
14110             len += cseq_len;
14111             line = cseq_len;
14112             clen = cseq_len;
14113             continue;
14114         }
14115
14116         if (dest)
14117             dest[len] = src[i];
14118         len++;
14119         if (!ansi)
14120             line++;
14121         if (src[i] == '\n')
14122             line = 0;
14123         if (src[i] == 'm')
14124             ansi = 0;
14125     }
14126     if (dest && appData.debugMode)
14127     {
14128         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14129             count, width, line, len, *lp);
14130         show_bytes(debugFP, src, count);
14131         fprintf(debugFP, "\ndest: ");
14132         show_bytes(debugFP, dest, len);
14133         fprintf(debugFP, "\n");
14134     }
14135     *lp = dest ? line : old_line;
14136
14137     return len;
14138 }