update year in copyright info
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252
253 /* States for ics_getting_history */
254 #define H_FALSE 0
255 #define H_REQUESTED 1
256 #define H_GOT_REQ_HEADER 2
257 #define H_GOT_UNREQ_HEADER 3
258 #define H_GETTING_MOVES 4
259 #define H_GOT_UNWANTED_HEADER 5
260
261 /* whosays values for GameEnds */
262 #define GE_ICS 0
263 #define GE_ENGINE 1
264 #define GE_PLAYER 2
265 #define GE_FILE 3
266 #define GE_XBOARD 4
267 #define GE_ENGINE1 5
268 #define GE_ENGINE2 6
269
270 /* Maximum number of games in a cmail message */
271 #define CMAIL_MAX_GAMES 20
272
273 /* Different types of move when calling RegisterMove */
274 #define CMAIL_MOVE   0
275 #define CMAIL_RESIGN 1
276 #define CMAIL_DRAW   2
277 #define CMAIL_ACCEPT 3
278
279 /* Different types of result to remember for each game */
280 #define CMAIL_NOT_RESULT 0
281 #define CMAIL_OLD_RESULT 1
282 #define CMAIL_NEW_RESULT 2
283
284 /* Telnet protocol constants */
285 #define TN_WILL 0373
286 #define TN_WONT 0374
287 #define TN_DO   0375
288 #define TN_DONT 0376
289 #define TN_IAC  0377
290 #define TN_ECHO 0001
291 #define TN_SGA  0003
292 #define TN_PORT 23
293
294 /* [AS] */
295 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 {
297     assert( dst != NULL );
298     assert( src != NULL );
299     assert( count > 0 );
300
301     strncpy( dst, src, count );
302     dst[ count-1 ] = '\0';
303     return dst;
304 }
305
306 /* Some compiler can't cast u64 to double
307  * This function do the job for us:
308
309  * We use the highest bit for cast, this only
310  * works if the highest bit is not
311  * in use (This should not happen)
312  *
313  * We used this for all compiler
314  */
315 double
316 u64ToDouble(u64 value)
317 {
318   double r;
319   u64 tmp = value & u64Const(0x7fffffffffffffff);
320   r = (double)(s64)tmp;
321   if (value & u64Const(0x8000000000000000))
322        r +=  9.2233720368547758080e18; /* 2^63 */
323  return r;
324 }
325
326 /* Fake up flags for now, as we aren't keeping track of castling
327    availability yet. [HGM] Change of logic: the flag now only
328    indicates the type of castlings allowed by the rule of the game.
329    The actual rights themselves are maintained in the array
330    castlingRights, as part of the game history, and are not probed
331    by this function.
332  */
333 int
334 PosFlags(index)
335 {
336   int flags = F_ALL_CASTLE_OK;
337   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
338   switch (gameInfo.variant) {
339   case VariantSuicide:
340     flags &= ~F_ALL_CASTLE_OK;
341   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
342     flags |= F_IGNORE_CHECK;
343   case VariantLosers:
344     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345     break;
346   case VariantAtomic:
347     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348     break;
349   case VariantKriegspiel:
350     flags |= F_KRIEGSPIEL_CAPTURE;
351     break;
352   case VariantCapaRandom: 
353   case VariantFischeRandom:
354     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
355   case VariantNoCastle:
356   case VariantShatranj:
357   case VariantCourier:
358     flags &= ~F_ALL_CASTLE_OK;
359     break;
360   default:
361     break;
362   }
363   return flags;
364 }
365
366 FILE *gameFileFP, *debugFP;
367
368 /* 
369     [AS] Note: sometimes, the sscanf() function is used to parse the input
370     into a fixed-size buffer. Because of this, we must be prepared to
371     receive strings as long as the size of the input buffer, which is currently
372     set to 4K for Windows and 8K for the rest.
373     So, we must either allocate sufficiently large buffers here, or
374     reduce the size of the input buffer in the input reading part.
375 */
376
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
379 char thinkOutput1[MSG_SIZ*10];
380
381 ChessProgramState first, second;
382
383 /* premove variables */
384 int premoveToX = 0;
385 int premoveToY = 0;
386 int premoveFromX = 0;
387 int premoveFromY = 0;
388 int premovePromoChar = 0;
389 int gotPremove = 0;
390 Boolean alarmSounded;
391 /* end premove variables */
392
393 char *ics_prefix = "$";
394 int ics_type = ICS_GENERIC;
395
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
397 int pauseExamForwardMostMove = 0;
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
404 int whiteFlag = FALSE, blackFlag = FALSE;
405 int userOfferedDraw = FALSE;
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
408 int cmailMoveType[CMAIL_MAX_GAMES];
409 long ics_clock_paused = 0;
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
412 GameMode gameMode = BeginningOfGame;
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
416 int hiddenThinkOutputState = 0; /* [AS] */
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
418 int adjudicateLossPlies = 6;
419 char white_holding[64], black_holding[64];
420 TimeMark lastNodeCountTime;
421 long lastNodeCount=0;
422 int have_sent_ICS_logon = 0;
423 int movesPerSession;
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
425 long timeControl_2; /* [AS] Allow separate time controls */
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
427 long timeRemaining[2][MAX_MOVES];
428 int matchGame = 0;
429 TimeMark programStartTime;
430 char ics_handle[MSG_SIZ];
431 int have_set_title = 0;
432
433 /* animateTraining preserves the state of appData.animate
434  * when Training mode is activated. This allows the
435  * response to be animated when appData.animate == TRUE and
436  * appData.animateDragging == TRUE.
437  */
438 Boolean animateTraining;
439
440 GameInfo gameInfo;
441
442 AppData appData;
443
444 Board boards[MAX_MOVES];
445 /* [HGM] Following 7 needed for accurate legality tests: */
446 signed char  epStatus[MAX_MOVES];
447 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
448 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
449 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int   initialRulePlies, FENrulePlies;
452 char  FENepStatus;
453 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int loadFlag = 0; 
455 int shuffleOpenings;
456 int mute; // mute all sounds
457
458 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
459     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
460         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
461     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
462         BlackKing, BlackBishop, BlackKnight, BlackRook }
463 };
464
465 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
466     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
467         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
468     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
469         BlackKing, BlackKing, BlackKnight, BlackRook }
470 };
471
472 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
473     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
474         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
475     { BlackRook, BlackMan, BlackBishop, BlackQueen,
476         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 };
478
479 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
480     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
481         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
482     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
483         BlackKing, BlackBishop, BlackKnight, BlackRook }
484 };
485
486 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
487     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
488         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
489     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
490         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
491 };
492
493
494 #if (BOARD_SIZE>=10)
495 ChessSquare ShogiArray[2][BOARD_SIZE] = {
496     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
497         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
498     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
499         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 };
501
502 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
504         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
506         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
510     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
511         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
513         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 };
515
516 ChessSquare GreatArray[2][BOARD_SIZE] = {
517     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
518         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
519     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
520         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 };
522
523 ChessSquare JanusArray[2][BOARD_SIZE] = {
524     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
525         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
526     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
527         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
528 };
529
530 #ifdef GOTHIC
531 ChessSquare GothicArray[2][BOARD_SIZE] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
533         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
535         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 };
537 #else // !GOTHIC
538 #define GothicArray CapablancaArray
539 #endif // !GOTHIC
540
541 #ifdef FALCON
542 ChessSquare FalconArray[2][BOARD_SIZE] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
544         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
546         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 };
548 #else // !FALCON
549 #define FalconArray CapablancaArray
550 #endif // !FALCON
551
552 #else // !(BOARD_SIZE>=10)
553 #define XiangqiPosition FIDEArray
554 #define CapablancaArray FIDEArray
555 #define GothicArray FIDEArray
556 #define GreatArray FIDEArray
557 #endif // !(BOARD_SIZE>=10)
558
559 #if (BOARD_SIZE>=12)
560 ChessSquare CourierArray[2][BOARD_SIZE] = {
561     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
562         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
563     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
564         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
565 };
566 #else // !(BOARD_SIZE>=12)
567 #define CourierArray CapablancaArray
568 #endif // !(BOARD_SIZE>=12)
569
570
571 Board initialPosition;
572
573
574 /* Convert str to a rating. Checks for special cases of "----",
575
576    "++++", etc. Also strips ()'s */
577 int
578 string_to_rating(str)
579   char *str;
580 {
581   while(*str && !isdigit(*str)) ++str;
582   if (!*str)
583     return 0;   /* One of the special "no rating" cases */
584   else
585     return atoi(str);
586 }
587
588 void
589 ClearProgramStats()
590 {
591     /* Init programStats */
592     programStats.movelist[0] = 0;
593     programStats.depth = 0;
594     programStats.nr_moves = 0;
595     programStats.moves_left = 0;
596     programStats.nodes = 0;
597     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
598     programStats.score = 0;
599     programStats.got_only_move = 0;
600     programStats.got_fail = 0;
601     programStats.line_is_book = 0;
602 }
603
604 void
605 InitBackEnd1()
606 {
607     int matched, min, sec;
608
609     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
610
611     GetTimeMark(&programStartTime);
612     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
613
614     ClearProgramStats();
615     programStats.ok_to_send = 1;
616     programStats.seen_stat = 0;
617
618     /*
619      * Initialize game list
620      */
621     ListNew(&gameList);
622
623
624     /*
625      * Internet chess server status
626      */
627     if (appData.icsActive) {
628         appData.matchMode = FALSE;
629         appData.matchGames = 0;
630 #if ZIPPY       
631         appData.noChessProgram = !appData.zippyPlay;
632 #else
633         appData.zippyPlay = FALSE;
634         appData.zippyTalk = FALSE;
635         appData.noChessProgram = TRUE;
636 #endif
637         if (*appData.icsHelper != NULLCHAR) {
638             appData.useTelnet = TRUE;
639             appData.telnetProgram = appData.icsHelper;
640         }
641     } else {
642         appData.zippyTalk = appData.zippyPlay = FALSE;
643     }
644
645     /* [AS] Initialize pv info list [HGM] and game state */
646     {
647         int i, j;
648
649         for( i=0; i<MAX_MOVES; i++ ) {
650             pvInfoList[i].depth = -1;
651             epStatus[i]=EP_NONE;
652             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
653         }
654     }
655
656     /*
657      * Parse timeControl resource
658      */
659     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
660                           appData.movesPerSession)) {
661         char buf[MSG_SIZ];
662         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
663         DisplayFatalError(buf, 0, 2);
664     }
665
666     /*
667      * Parse searchTime resource
668      */
669     if (*appData.searchTime != NULLCHAR) {
670         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
671         if (matched == 1) {
672             searchTime = min * 60;
673         } else if (matched == 2) {
674             searchTime = min * 60 + sec;
675         } else {
676             char buf[MSG_SIZ];
677             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
678             DisplayFatalError(buf, 0, 2);
679         }
680     }
681
682     /* [AS] Adjudication threshold */
683     adjudicateLossThreshold = appData.adjudicateLossThreshold;
684     
685     first.which = "first";
686     second.which = "second";
687     first.maybeThinking = second.maybeThinking = FALSE;
688     first.pr = second.pr = NoProc;
689     first.isr = second.isr = NULL;
690     first.sendTime = second.sendTime = 2;
691     first.sendDrawOffers = 1;
692     if (appData.firstPlaysBlack) {
693         first.twoMachinesColor = "black\n";
694         second.twoMachinesColor = "white\n";
695     } else {
696         first.twoMachinesColor = "white\n";
697         second.twoMachinesColor = "black\n";
698     }
699     first.program = appData.firstChessProgram;
700     second.program = appData.secondChessProgram;
701     first.host = appData.firstHost;
702     second.host = appData.secondHost;
703     first.dir = appData.firstDirectory;
704     second.dir = appData.secondDirectory;
705     first.other = &second;
706     second.other = &first;
707     first.initString = appData.initString;
708     second.initString = appData.secondInitString;
709     first.computerString = appData.firstComputerString;
710     second.computerString = appData.secondComputerString;
711     first.useSigint = second.useSigint = TRUE;
712     first.useSigterm = second.useSigterm = TRUE;
713     first.reuse = appData.reuseFirst;
714     second.reuse = appData.reuseSecond;
715     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
716     second.nps = appData.secondNPS;
717     first.useSetboard = second.useSetboard = FALSE;
718     first.useSAN = second.useSAN = FALSE;
719     first.usePing = second.usePing = FALSE;
720     first.lastPing = second.lastPing = 0;
721     first.lastPong = second.lastPong = 0;
722     first.usePlayother = second.usePlayother = FALSE;
723     first.useColors = second.useColors = TRUE;
724     first.useUsermove = second.useUsermove = FALSE;
725     first.sendICS = second.sendICS = FALSE;
726     first.sendName = second.sendName = appData.icsActive;
727     first.sdKludge = second.sdKludge = FALSE;
728     first.stKludge = second.stKludge = FALSE;
729     TidyProgramName(first.program, first.host, first.tidy);
730     TidyProgramName(second.program, second.host, second.tidy);
731     first.matchWins = second.matchWins = 0;
732     strcpy(first.variants, appData.variant);
733     strcpy(second.variants, appData.variant);
734     first.analysisSupport = second.analysisSupport = 2; /* detect */
735     first.analyzing = second.analyzing = FALSE;
736     first.initDone = second.initDone = FALSE;
737
738     /* New features added by Tord: */
739     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
740     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
741     /* End of new features added by Tord. */
742     first.fenOverride  = appData.fenOverride1;
743     second.fenOverride = appData.fenOverride2;
744
745     /* [HGM] time odds: set factor for each machine */
746     first.timeOdds  = appData.firstTimeOdds;
747     second.timeOdds = appData.secondTimeOdds;
748     { float norm = 1;
749         if(appData.timeOddsMode) {
750             norm = first.timeOdds;
751             if(norm > second.timeOdds) norm = second.timeOdds;
752         }
753         first.timeOdds /= norm;
754         second.timeOdds /= norm;
755     }
756
757     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
758     first.accumulateTC = appData.firstAccumulateTC;
759     second.accumulateTC = appData.secondAccumulateTC;
760     first.maxNrOfSessions = second.maxNrOfSessions = 1;
761
762     /* [HGM] debug */
763     first.debug = second.debug = FALSE;
764     first.supportsNPS = second.supportsNPS = UNKNOWN;
765
766     /* [HGM] options */
767     first.optionSettings  = appData.firstOptions;
768     second.optionSettings = appData.secondOptions;
769
770     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
771     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
772     first.isUCI = appData.firstIsUCI; /* [AS] */
773     second.isUCI = appData.secondIsUCI; /* [AS] */
774     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
775     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
776
777     if (appData.firstProtocolVersion > PROTOVER ||
778         appData.firstProtocolVersion < 1) {
779       char buf[MSG_SIZ];
780       sprintf(buf, _("protocol version %d not supported"),
781               appData.firstProtocolVersion);
782       DisplayFatalError(buf, 0, 2);
783     } else {
784       first.protocolVersion = appData.firstProtocolVersion;
785     }
786
787     if (appData.secondProtocolVersion > PROTOVER ||
788         appData.secondProtocolVersion < 1) {
789       char buf[MSG_SIZ];
790       sprintf(buf, _("protocol version %d not supported"),
791               appData.secondProtocolVersion);
792       DisplayFatalError(buf, 0, 2);
793     } else {
794       second.protocolVersion = appData.secondProtocolVersion;
795     }
796
797     if (appData.icsActive) {
798         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
799     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
800         appData.clockMode = FALSE;
801         first.sendTime = second.sendTime = 0;
802     }
803     
804 #if ZIPPY
805     /* Override some settings from environment variables, for backward
806        compatibility.  Unfortunately it's not feasible to have the env
807        vars just set defaults, at least in xboard.  Ugh.
808     */
809     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
810       ZippyInit();
811     }
812 #endif
813     
814     if (appData.noChessProgram) {
815         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
816         sprintf(programVersion, "%s", PACKAGE_STRING);
817     } else {
818       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
819       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
820       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821     }
822
823     if (!appData.icsActive) {
824       char buf[MSG_SIZ];
825       /* Check for variants that are supported only in ICS mode,
826          or not at all.  Some that are accepted here nevertheless
827          have bugs; see comments below.
828       */
829       VariantClass variant = StringToVariant(appData.variant);
830       switch (variant) {
831       case VariantBughouse:     /* need four players and two boards */
832       case VariantKriegspiel:   /* need to hide pieces and move details */
833       /* case VariantFischeRandom: (Fabien: moved below) */
834         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
835         DisplayFatalError(buf, 0, 2);
836         return;
837
838       case VariantUnknown:
839       case VariantLoadable:
840       case Variant29:
841       case Variant30:
842       case Variant31:
843       case Variant32:
844       case Variant33:
845       case Variant34:
846       case Variant35:
847       case Variant36:
848       default:
849         sprintf(buf, _("Unknown variant name %s"), appData.variant);
850         DisplayFatalError(buf, 0, 2);
851         return;
852
853       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
854       case VariantFairy:      /* [HGM] TestLegality definitely off! */
855       case VariantGothic:     /* [HGM] should work */
856       case VariantCapablanca: /* [HGM] should work */
857       case VariantCourier:    /* [HGM] initial forced moves not implemented */
858       case VariantShogi:      /* [HGM] drops not tested for legality */
859       case VariantKnightmate: /* [HGM] should work */
860       case VariantCylinder:   /* [HGM] untested */
861       case VariantFalcon:     /* [HGM] untested */
862       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
863                                  offboard interposition not understood */
864       case VariantNormal:     /* definitely works! */
865       case VariantWildCastle: /* pieces not automatically shuffled */
866       case VariantNoCastle:   /* pieces not automatically shuffled */
867       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
868       case VariantLosers:     /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantSuicide:    /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantGiveaway:   /* should work except for win condition,
873                                  and doesn't know captures are mandatory */
874       case VariantTwoKings:   /* should work */
875       case VariantAtomic:     /* should work except for win condition */
876       case Variant3Check:     /* should work except for win condition */
877       case VariantShatranj:   /* should work except for all win conditions */
878       case VariantBerolina:   /* might work if TestLegality is off */
879       case VariantCapaRandom: /* should work */
880       case VariantJanus:      /* should work */
881       case VariantSuper:      /* experimental */
882       case VariantGreat:      /* experimental, requires legality testing to be off */
883         break;
884       }
885     }
886
887     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
888     InitEngineUCI( installDir, &second );
889 }
890
891 int NextIntegerFromString( char ** str, long * value )
892 {
893     int result = -1;
894     char * s = *str;
895
896     while( *s == ' ' || *s == '\t' ) {
897         s++;
898     }
899
900     *value = 0;
901
902     if( *s >= '0' && *s <= '9' ) {
903         while( *s >= '0' && *s <= '9' ) {
904             *value = *value * 10 + (*s - '0');
905             s++;
906         }
907
908         result = 0;
909     }
910
911     *str = s;
912
913     return result;
914 }
915
916 int NextTimeControlFromString( char ** str, long * value )
917 {
918     long temp;
919     int result = NextIntegerFromString( str, &temp );
920
921     if( result == 0 ) {
922         *value = temp * 60; /* Minutes */
923         if( **str == ':' ) {
924             (*str)++;
925             result = NextIntegerFromString( str, &temp );
926             *value += temp; /* Seconds */
927         }
928     }
929
930     return result;
931 }
932
933 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
934 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
935     int result = -1; long temp, temp2;
936
937     if(**str != '+') return -1; // old params remain in force!
938     (*str)++;
939     if( NextTimeControlFromString( str, &temp ) ) return -1;
940
941     if(**str != '/') {
942         /* time only: incremental or sudden-death time control */
943         if(**str == '+') { /* increment follows; read it */
944             (*str)++;
945             if(result = NextIntegerFromString( str, &temp2)) return -1;
946             *inc = temp2 * 1000;
947         } else *inc = 0;
948         *moves = 0; *tc = temp * 1000; 
949         return 0;
950     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
951
952     (*str)++; /* classical time control */
953     result = NextTimeControlFromString( str, &temp2);
954     if(result == 0) {
955         *moves = temp/60;
956         *tc    = temp2 * 1000;
957         *inc   = 0;
958     }
959     return result;
960 }
961
962 int GetTimeQuota(int movenr)
963 {   /* [HGM] get time to add from the multi-session time-control string */
964     int moves=1; /* kludge to force reading of first session */
965     long time, increment;
966     char *s = fullTimeControlString;
967
968     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
969     do {
970         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
971         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
972         if(movenr == -1) return time;    /* last move before new session     */
973         if(!moves) return increment;     /* current session is incremental   */
974         if(movenr >= 0) movenr -= moves; /* we already finished this session */
975     } while(movenr >= -1);               /* try again for next session       */
976
977     return 0; // no new time quota on this move
978 }
979
980 int
981 ParseTimeControl(tc, ti, mps)
982      char *tc;
983      int ti;
984      int mps;
985 {
986   long tc1;
987   long tc2;
988   char buf[MSG_SIZ];
989   
990   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991   if(ti > 0) {
992     if(mps)
993       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
994     else sprintf(buf, "+%s+%d", tc, ti);
995   } else {
996     if(mps)
997              sprintf(buf, "+%d/%s", mps, tc);
998     else sprintf(buf, "+%s", tc);
999   }
1000   fullTimeControlString = StrSave(buf);
1001   
1002   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1003     return FALSE;
1004   }
1005   
1006   if( *tc == '/' ) {
1007     /* Parse second time control */
1008     tc++;
1009     
1010     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1011       return FALSE;
1012     }
1013     
1014     if( tc2 == 0 ) {
1015       return FALSE;
1016     }
1017     
1018     timeControl_2 = tc2 * 1000;
1019   }
1020   else {
1021     timeControl_2 = 0;
1022   }
1023   
1024   if( tc1 == 0 ) {
1025     return FALSE;
1026   }
1027   
1028   timeControl = tc1 * 1000;
1029   
1030   if (ti >= 0) {
1031     timeIncrement = ti * 1000;  /* convert to ms */
1032     movesPerSession = 0;
1033   } else {
1034     timeIncrement = 0;
1035     movesPerSession = mps;
1036   }
1037   return TRUE;
1038 }
1039
1040 void
1041 InitBackEnd2()
1042 {
1043     if (appData.debugMode) {
1044         fprintf(debugFP, "%s\n", programVersion);
1045     }
1046
1047     set_cont_sequence(appData.wrapContSeq);
1048     if (appData.matchGames > 0) {
1049         appData.matchMode = TRUE;
1050     } else if (appData.matchMode) {
1051         appData.matchGames = 1;
1052     }
1053     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1054         appData.matchGames = appData.sameColorGames;
1055     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1056         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1057         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058     }
1059     Reset(TRUE, FALSE);
1060     if (appData.noChessProgram || first.protocolVersion == 1) {
1061       InitBackEnd3();
1062     } else {
1063       /* kludge: allow timeout for initial "feature" commands */
1064       FreezeUI();
1065       DisplayMessage("", _("Starting chess program"));
1066       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1067     }
1068 }
1069
1070 void
1071 InitBackEnd3 P((void))
1072 {
1073     GameMode initialMode;
1074     char buf[MSG_SIZ];
1075     int err;
1076
1077     InitChessProgram(&first, startedFromSetupPosition);
1078
1079
1080     if (appData.icsActive) {
1081 #ifdef WIN32
1082         /* [DM] Make a console window if needed [HGM] merged ifs */
1083         ConsoleCreate(); 
1084 #endif
1085         err = establish();
1086         if (err != 0) {
1087             if (*appData.icsCommPort != NULLCHAR) {
1088                 sprintf(buf, _("Could not open comm port %s"),  
1089                         appData.icsCommPort);
1090             } else {
1091                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1092                         appData.icsHost, appData.icsPort);
1093             }
1094             DisplayFatalError(buf, err, 1);
1095             return;
1096         }
1097         SetICSMode();
1098         telnetISR =
1099           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1100         fromUserISR =
1101           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1102         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1103             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1104     } else if (appData.noChessProgram) {
1105         SetNCPMode();
1106     } else {
1107         SetGNUMode();
1108     }
1109
1110     if (*appData.cmailGameName != NULLCHAR) {
1111         SetCmailMode();
1112         OpenLoopback(&cmailPR);
1113         cmailISR =
1114           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1115     }
1116     
1117     ThawUI();
1118     DisplayMessage("", "");
1119     if (StrCaseCmp(appData.initialMode, "") == 0) {
1120       initialMode = BeginningOfGame;
1121     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1122       initialMode = TwoMachinesPlay;
1123     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1124       initialMode = AnalyzeFile; 
1125     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1126       initialMode = AnalyzeMode;
1127     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1128       initialMode = MachinePlaysWhite;
1129     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1130       initialMode = MachinePlaysBlack;
1131     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1132       initialMode = EditGame;
1133     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1134       initialMode = EditPosition;
1135     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1136       initialMode = Training;
1137     } else {
1138       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1139       DisplayFatalError(buf, 0, 2);
1140       return;
1141     }
1142
1143     if (appData.matchMode) {
1144         /* Set up machine vs. machine match */
1145         if (appData.noChessProgram) {
1146             DisplayFatalError(_("Can't have a match with no chess programs"),
1147                               0, 2);
1148             return;
1149         }
1150         matchMode = TRUE;
1151         matchGame = 1;
1152         if (*appData.loadGameFile != NULLCHAR) {
1153             int index = appData.loadGameIndex; // [HGM] autoinc
1154             if(index<0) lastIndex = index = 1;
1155             if (!LoadGameFromFile(appData.loadGameFile,
1156                                   index,
1157                                   appData.loadGameFile, FALSE)) {
1158                 DisplayFatalError(_("Bad game file"), 0, 1);
1159                 return;
1160             }
1161         } else if (*appData.loadPositionFile != NULLCHAR) {
1162             int index = appData.loadPositionIndex; // [HGM] autoinc
1163             if(index<0) lastIndex = index = 1;
1164             if (!LoadPositionFromFile(appData.loadPositionFile,
1165                                       index,
1166                                       appData.loadPositionFile)) {
1167                 DisplayFatalError(_("Bad position file"), 0, 1);
1168                 return;
1169             }
1170         }
1171         TwoMachinesEvent();
1172     } else if (*appData.cmailGameName != NULLCHAR) {
1173         /* Set up cmail mode */
1174         ReloadCmailMsgEvent(TRUE);
1175     } else {
1176         /* Set up other modes */
1177         if (initialMode == AnalyzeFile) {
1178           if (*appData.loadGameFile == NULLCHAR) {
1179             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1180             return;
1181           }
1182         }
1183         if (*appData.loadGameFile != NULLCHAR) {
1184             (void) LoadGameFromFile(appData.loadGameFile,
1185                                     appData.loadGameIndex,
1186                                     appData.loadGameFile, TRUE);
1187         } else if (*appData.loadPositionFile != NULLCHAR) {
1188             (void) LoadPositionFromFile(appData.loadPositionFile,
1189                                         appData.loadPositionIndex,
1190                                         appData.loadPositionFile);
1191             /* [HGM] try to make self-starting even after FEN load */
1192             /* to allow automatic setup of fairy variants with wtm */
1193             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1194                 gameMode = BeginningOfGame;
1195                 setboardSpoiledMachineBlack = 1;
1196             }
1197             /* [HGM] loadPos: make that every new game uses the setup */
1198             /* from file as long as we do not switch variant          */
1199             if(!blackPlaysFirst) { int i;
1200                 startedFromPositionFile = TRUE;
1201                 CopyBoard(filePosition, boards[0]);
1202                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1203             }
1204         }
1205         if (initialMode == AnalyzeMode) {
1206           if (appData.noChessProgram) {
1207             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1208             return;
1209           }
1210           if (appData.icsActive) {
1211             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1212             return;
1213           }
1214           AnalyzeModeEvent();
1215         } else if (initialMode == AnalyzeFile) {
1216           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1217           ShowThinkingEvent();
1218           AnalyzeFileEvent();
1219           AnalysisPeriodicEvent(1);
1220         } else if (initialMode == MachinePlaysWhite) {
1221           if (appData.noChessProgram) {
1222             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1223                               0, 2);
1224             return;
1225           }
1226           if (appData.icsActive) {
1227             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1228                               0, 2);
1229             return;
1230           }
1231           MachineWhiteEvent();
1232         } else if (initialMode == MachinePlaysBlack) {
1233           if (appData.noChessProgram) {
1234             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1235                               0, 2);
1236             return;
1237           }
1238           if (appData.icsActive) {
1239             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1240                               0, 2);
1241             return;
1242           }
1243           MachineBlackEvent();
1244         } else if (initialMode == TwoMachinesPlay) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           TwoMachinesEvent();
1256         } else if (initialMode == EditGame) {
1257           EditGameEvent();
1258         } else if (initialMode == EditPosition) {
1259           EditPositionEvent();
1260         } else if (initialMode == Training) {
1261           if (*appData.loadGameFile == NULLCHAR) {
1262             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1263             return;
1264           }
1265           TrainingEvent();
1266         }
1267     }
1268 }
1269
1270 /*
1271  * Establish will establish a contact to a remote host.port.
1272  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1273  *  used to talk to the host.
1274  * Returns 0 if okay, error code if not.
1275  */
1276 int
1277 establish()
1278 {
1279     char buf[MSG_SIZ];
1280
1281     if (*appData.icsCommPort != NULLCHAR) {
1282         /* Talk to the host through a serial comm port */
1283         return OpenCommPort(appData.icsCommPort, &icsPR);
1284
1285     } else if (*appData.gateway != NULLCHAR) {
1286         if (*appData.remoteShell == NULLCHAR) {
1287             /* Use the rcmd protocol to run telnet program on a gateway host */
1288             snprintf(buf, sizeof(buf), "%s %s %s",
1289                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1290             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1291
1292         } else {
1293             /* Use the rsh program to run telnet program on a gateway host */
1294             if (*appData.remoteUser == NULLCHAR) {
1295                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1296                         appData.gateway, appData.telnetProgram,
1297                         appData.icsHost, appData.icsPort);
1298             } else {
1299                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1300                         appData.remoteShell, appData.gateway, 
1301                         appData.remoteUser, appData.telnetProgram,
1302                         appData.icsHost, appData.icsPort);
1303             }
1304             return StartChildProcess(buf, "", &icsPR);
1305
1306         }
1307     } else if (appData.useTelnet) {
1308         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1309
1310     } else {
1311         /* TCP socket interface differs somewhat between
1312            Unix and NT; handle details in the front end.
1313            */
1314         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315     }
1316 }
1317
1318 void
1319 show_bytes(fp, buf, count)
1320      FILE *fp;
1321      char *buf;
1322      int count;
1323 {
1324     while (count--) {
1325         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1326             fprintf(fp, "\\%03o", *buf & 0xff);
1327         } else {
1328             putc(*buf, fp);
1329         }
1330         buf++;
1331     }
1332     fflush(fp);
1333 }
1334
1335 /* Returns an errno value */
1336 int
1337 OutputMaybeTelnet(pr, message, count, outError)
1338      ProcRef pr;
1339      char *message;
1340      int count;
1341      int *outError;
1342 {
1343     char buf[8192], *p, *q, *buflim;
1344     int left, newcount, outcount;
1345
1346     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1347         *appData.gateway != NULLCHAR) {
1348         if (appData.debugMode) {
1349             fprintf(debugFP, ">ICS: ");
1350             show_bytes(debugFP, message, count);
1351             fprintf(debugFP, "\n");
1352         }
1353         return OutputToProcess(pr, message, count, outError);
1354     }
1355
1356     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1357     p = message;
1358     q = buf;
1359     left = count;
1360     newcount = 0;
1361     while (left) {
1362         if (q >= buflim) {
1363             if (appData.debugMode) {
1364                 fprintf(debugFP, ">ICS: ");
1365                 show_bytes(debugFP, buf, newcount);
1366                 fprintf(debugFP, "\n");
1367             }
1368             outcount = OutputToProcess(pr, buf, newcount, outError);
1369             if (outcount < newcount) return -1; /* to be sure */
1370             q = buf;
1371             newcount = 0;
1372         }
1373         if (*p == '\n') {
1374             *q++ = '\r';
1375             newcount++;
1376         } else if (((unsigned char) *p) == TN_IAC) {
1377             *q++ = (char) TN_IAC;
1378             newcount ++;
1379         }
1380         *q++ = *p++;
1381         newcount++;
1382         left--;
1383     }
1384     if (appData.debugMode) {
1385         fprintf(debugFP, ">ICS: ");
1386         show_bytes(debugFP, buf, newcount);
1387         fprintf(debugFP, "\n");
1388     }
1389     outcount = OutputToProcess(pr, buf, newcount, outError);
1390     if (outcount < newcount) return -1; /* to be sure */
1391     return count;
1392 }
1393
1394 void
1395 read_from_player(isr, closure, message, count, error)
1396      InputSourceRef isr;
1397      VOIDSTAR closure;
1398      char *message;
1399      int count;
1400      int error;
1401 {
1402     int outError, outCount;
1403     static int gotEof = 0;
1404
1405     /* Pass data read from player on to ICS */
1406     if (count > 0) {
1407         gotEof = 0;
1408         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1409         if (outCount < count) {
1410             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1411         }
1412     } else if (count < 0) {
1413         RemoveInputSource(isr);
1414         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1415     } else if (gotEof++ > 0) {
1416         RemoveInputSource(isr);
1417         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418     }
1419 }
1420
1421 void
1422 KeepAlive()
1423 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1424     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1425     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1426     SendToICS("date\n");
1427     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1428 }
1429
1430 /* added routine for printf style output to ics */
1431 void ics_printf(char *format, ...)
1432 {
1433     char buffer[MSG_SIZ];
1434     va_list args;
1435
1436     va_start(args, format);
1437     vsnprintf(buffer, sizeof(buffer), format, args);
1438     buffer[sizeof(buffer)-1] = '\0';
1439     SendToICS(buffer);
1440     va_end(args);
1441 }
1442
1443 void
1444 SendToICS(s)
1445      char *s;
1446 {
1447     int count, outCount, outError;
1448
1449     if (icsPR == NULL) return;
1450
1451     count = strlen(s);
1452     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1453     if (outCount < count) {
1454         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1455     }
1456 }
1457
1458 /* This is used for sending logon scripts to the ICS. Sending
1459    without a delay causes problems when using timestamp on ICC
1460    (at least on my machine). */
1461 void
1462 SendToICSDelayed(s,msdelay)
1463      char *s;
1464      long msdelay;
1465 {
1466     int count, outCount, outError;
1467
1468     if (icsPR == NULL) return;
1469
1470     count = strlen(s);
1471     if (appData.debugMode) {
1472         fprintf(debugFP, ">ICS: ");
1473         show_bytes(debugFP, s, count);
1474         fprintf(debugFP, "\n");
1475     }
1476     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1477                                       msdelay);
1478     if (outCount < count) {
1479         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480     }
1481 }
1482
1483
1484 /* Remove all highlighting escape sequences in s
1485    Also deletes any suffix starting with '(' 
1486    */
1487 char *
1488 StripHighlightAndTitle(s)
1489      char *s;
1490 {
1491     static char retbuf[MSG_SIZ];
1492     char *p = retbuf;
1493
1494     while (*s != NULLCHAR) {
1495         while (*s == '\033') {
1496             while (*s != NULLCHAR && !isalpha(*s)) s++;
1497             if (*s != NULLCHAR) s++;
1498         }
1499         while (*s != NULLCHAR && *s != '\033') {
1500             if (*s == '(' || *s == '[') {
1501                 *p = NULLCHAR;
1502                 return retbuf;
1503             }
1504             *p++ = *s++;
1505         }
1506     }
1507     *p = NULLCHAR;
1508     return retbuf;
1509 }
1510
1511 /* Remove all highlighting escape sequences in s */
1512 char *
1513 StripHighlight(s)
1514      char *s;
1515 {
1516     static char retbuf[MSG_SIZ];
1517     char *p = retbuf;
1518
1519     while (*s != NULLCHAR) {
1520         while (*s == '\033') {
1521             while (*s != NULLCHAR && !isalpha(*s)) s++;
1522             if (*s != NULLCHAR) s++;
1523         }
1524         while (*s != NULLCHAR && *s != '\033') {
1525             *p++ = *s++;
1526         }
1527     }
1528     *p = NULLCHAR;
1529     return retbuf;
1530 }
1531
1532 char *variantNames[] = VARIANT_NAMES;
1533 char *
1534 VariantName(v)
1535      VariantClass v;
1536 {
1537     return variantNames[v];
1538 }
1539
1540
1541 /* Identify a variant from the strings the chess servers use or the
1542    PGN Variant tag names we use. */
1543 VariantClass
1544 StringToVariant(e)
1545      char *e;
1546 {
1547     char *p;
1548     int wnum = -1;
1549     VariantClass v = VariantNormal;
1550     int i, found = FALSE;
1551     char buf[MSG_SIZ];
1552
1553     if (!e) return v;
1554
1555     /* [HGM] skip over optional board-size prefixes */
1556     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1557         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1558         while( *e++ != '_');
1559     }
1560
1561     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1562         v = VariantNormal;
1563         found = TRUE;
1564     } else
1565     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1566       if (StrCaseStr(e, variantNames[i])) {
1567         v = (VariantClass) i;
1568         found = TRUE;
1569         break;
1570       }
1571     }
1572
1573     if (!found) {
1574       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1575           || StrCaseStr(e, "wild/fr") 
1576           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1577         v = VariantFischeRandom;
1578       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1579                  (i = 1, p = StrCaseStr(e, "w"))) {
1580         p += i;
1581         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582         if (isdigit(*p)) {
1583           wnum = atoi(p);
1584         } else {
1585           wnum = -1;
1586         }
1587         switch (wnum) {
1588         case 0: /* FICS only, actually */
1589         case 1:
1590           /* Castling legal even if K starts on d-file */
1591           v = VariantWildCastle;
1592           break;
1593         case 2:
1594         case 3:
1595         case 4:
1596           /* Castling illegal even if K & R happen to start in
1597              normal positions. */
1598           v = VariantNoCastle;
1599           break;
1600         case 5:
1601         case 7:
1602         case 8:
1603         case 10:
1604         case 11:
1605         case 12:
1606         case 13:
1607         case 14:
1608         case 15:
1609         case 18:
1610         case 19:
1611           /* Castling legal iff K & R start in normal positions */
1612           v = VariantNormal;
1613           break;
1614         case 6:
1615         case 20:
1616         case 21:
1617           /* Special wilds for position setup; unclear what to do here */
1618           v = VariantLoadable;
1619           break;
1620         case 9:
1621           /* Bizarre ICC game */
1622           v = VariantTwoKings;
1623           break;
1624         case 16:
1625           v = VariantKriegspiel;
1626           break;
1627         case 17:
1628           v = VariantLosers;
1629           break;
1630         case 22:
1631           v = VariantFischeRandom;
1632           break;
1633         case 23:
1634           v = VariantCrazyhouse;
1635           break;
1636         case 24:
1637           v = VariantBughouse;
1638           break;
1639         case 25:
1640           v = Variant3Check;
1641           break;
1642         case 26:
1643           /* Not quite the same as FICS suicide! */
1644           v = VariantGiveaway;
1645           break;
1646         case 27:
1647           v = VariantAtomic;
1648           break;
1649         case 28:
1650           v = VariantShatranj;
1651           break;
1652
1653         /* Temporary names for future ICC types.  The name *will* change in 
1654            the next xboard/WinBoard release after ICC defines it. */
1655         case 29:
1656           v = Variant29;
1657           break;
1658         case 30:
1659           v = Variant30;
1660           break;
1661         case 31:
1662           v = Variant31;
1663           break;
1664         case 32:
1665           v = Variant32;
1666           break;
1667         case 33:
1668           v = Variant33;
1669           break;
1670         case 34:
1671           v = Variant34;
1672           break;
1673         case 35:
1674           v = Variant35;
1675           break;
1676         case 36:
1677           v = Variant36;
1678           break;
1679         case 37:
1680           v = VariantShogi;
1681           break;
1682         case 38:
1683           v = VariantXiangqi;
1684           break;
1685         case 39:
1686           v = VariantCourier;
1687           break;
1688         case 40:
1689           v = VariantGothic;
1690           break;
1691         case 41:
1692           v = VariantCapablanca;
1693           break;
1694         case 42:
1695           v = VariantKnightmate;
1696           break;
1697         case 43:
1698           v = VariantFairy;
1699           break;
1700         case 44:
1701           v = VariantCylinder;
1702           break;
1703         case 45:
1704           v = VariantFalcon;
1705           break;
1706         case 46:
1707           v = VariantCapaRandom;
1708           break;
1709         case 47:
1710           v = VariantBerolina;
1711           break;
1712         case 48:
1713           v = VariantJanus;
1714           break;
1715         case 49:
1716           v = VariantSuper;
1717           break;
1718         case 50:
1719           v = VariantGreat;
1720           break;
1721         case -1:
1722           /* Found "wild" or "w" in the string but no number;
1723              must assume it's normal chess. */
1724           v = VariantNormal;
1725           break;
1726         default:
1727           sprintf(buf, _("Unknown wild type %d"), wnum);
1728           DisplayError(buf, 0);
1729           v = VariantUnknown;
1730           break;
1731         }
1732       }
1733     }
1734     if (appData.debugMode) {
1735       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1736               e, wnum, VariantName(v));
1737     }
1738     return v;
1739 }
1740
1741 static int leftover_start = 0, leftover_len = 0;
1742 char star_match[STAR_MATCH_N][MSG_SIZ];
1743
1744 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1745    advance *index beyond it, and set leftover_start to the new value of
1746    *index; else return FALSE.  If pattern contains the character '*', it
1747    matches any sequence of characters not containing '\r', '\n', or the
1748    character following the '*' (if any), and the matched sequence(s) are
1749    copied into star_match.
1750    */
1751 int
1752 looking_at(buf, index, pattern)
1753      char *buf;
1754      int *index;
1755      char *pattern;
1756 {
1757     char *bufp = &buf[*index], *patternp = pattern;
1758     int star_count = 0;
1759     char *matchp = star_match[0];
1760     
1761     for (;;) {
1762         if (*patternp == NULLCHAR) {
1763             *index = leftover_start = bufp - buf;
1764             *matchp = NULLCHAR;
1765             return TRUE;
1766         }
1767         if (*bufp == NULLCHAR) return FALSE;
1768         if (*patternp == '*') {
1769             if (*bufp == *(patternp + 1)) {
1770                 *matchp = NULLCHAR;
1771                 matchp = star_match[++star_count];
1772                 patternp += 2;
1773                 bufp++;
1774                 continue;
1775             } else if (*bufp == '\n' || *bufp == '\r') {
1776                 patternp++;
1777                 if (*patternp == NULLCHAR)
1778                   continue;
1779                 else
1780                   return FALSE;
1781             } else {
1782                 *matchp++ = *bufp++;
1783                 continue;
1784             }
1785         }
1786         if (*patternp != *bufp) return FALSE;
1787         patternp++;
1788         bufp++;
1789     }
1790 }
1791
1792 void
1793 SendToPlayer(data, length)
1794      char *data;
1795      int length;
1796 {
1797     int error, outCount;
1798     outCount = OutputToProcess(NoProc, data, length, &error);
1799     if (outCount < length) {
1800         DisplayFatalError(_("Error writing to display"), error, 1);
1801     }
1802 }
1803
1804 void
1805 PackHolding(packed, holding)
1806      char packed[];
1807      char *holding;
1808 {
1809     char *p = holding;
1810     char *q = packed;
1811     int runlength = 0;
1812     int curr = 9999;
1813     do {
1814         if (*p == curr) {
1815             runlength++;
1816         } else {
1817             switch (runlength) {
1818               case 0:
1819                 break;
1820               case 1:
1821                 *q++ = curr;
1822                 break;
1823               case 2:
1824                 *q++ = curr;
1825                 *q++ = curr;
1826                 break;
1827               default:
1828                 sprintf(q, "%d", runlength);
1829                 while (*q) q++;
1830                 *q++ = curr;
1831                 break;
1832             }
1833             runlength = 1;
1834             curr = *p;
1835         }
1836     } while (*p++);
1837     *q = NULLCHAR;
1838 }
1839
1840 /* Telnet protocol requests from the front end */
1841 void
1842 TelnetRequest(ddww, option)
1843      unsigned char ddww, option;
1844 {
1845     unsigned char msg[3];
1846     int outCount, outError;
1847
1848     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1849
1850     if (appData.debugMode) {
1851         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1852         switch (ddww) {
1853           case TN_DO:
1854             ddwwStr = "DO";
1855             break;
1856           case TN_DONT:
1857             ddwwStr = "DONT";
1858             break;
1859           case TN_WILL:
1860             ddwwStr = "WILL";
1861             break;
1862           case TN_WONT:
1863             ddwwStr = "WONT";
1864             break;
1865           default:
1866             ddwwStr = buf1;
1867             sprintf(buf1, "%d", ddww);
1868             break;
1869         }
1870         switch (option) {
1871           case TN_ECHO:
1872             optionStr = "ECHO";
1873             break;
1874           default:
1875             optionStr = buf2;
1876             sprintf(buf2, "%d", option);
1877             break;
1878         }
1879         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1880     }
1881     msg[0] = TN_IAC;
1882     msg[1] = ddww;
1883     msg[2] = option;
1884     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1885     if (outCount < 3) {
1886         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887     }
1888 }
1889
1890 void
1891 DoEcho()
1892 {
1893     if (!appData.icsActive) return;
1894     TelnetRequest(TN_DO, TN_ECHO);
1895 }
1896
1897 void
1898 DontEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DONT, TN_ECHO);
1902 }
1903
1904 void
1905 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1906 {
1907     /* put the holdings sent to us by the server on the board holdings area */
1908     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1909     char p;
1910     ChessSquare piece;
1911
1912     if(gameInfo.holdingsWidth < 2)  return;
1913     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1914         return; // prevent overwriting by pre-board holdings
1915
1916     if( (int)lowestPiece >= BlackPawn ) {
1917         holdingsColumn = 0;
1918         countsColumn = 1;
1919         holdingsStartRow = BOARD_HEIGHT-1;
1920         direction = -1;
1921     } else {
1922         holdingsColumn = BOARD_WIDTH-1;
1923         countsColumn = BOARD_WIDTH-2;
1924         holdingsStartRow = 0;
1925         direction = 1;
1926     }
1927
1928     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1929         board[i][holdingsColumn] = EmptySquare;
1930         board[i][countsColumn]   = (ChessSquare) 0;
1931     }
1932     while( (p=*holdings++) != NULLCHAR ) {
1933         piece = CharToPiece( ToUpper(p) );
1934         if(piece == EmptySquare) continue;
1935         /*j = (int) piece - (int) WhitePawn;*/
1936         j = PieceToNumber(piece);
1937         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1938         if(j < 0) continue;               /* should not happen */
1939         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1940         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1941         board[holdingsStartRow+j*direction][countsColumn]++;
1942     }
1943 }
1944
1945
1946 void
1947 VariantSwitch(Board board, VariantClass newVariant)
1948 {
1949    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1950    Board oldBoard;
1951
1952    startedFromPositionFile = FALSE;
1953    if(gameInfo.variant == newVariant) return;
1954
1955    /* [HGM] This routine is called each time an assignment is made to
1956     * gameInfo.variant during a game, to make sure the board sizes
1957     * are set to match the new variant. If that means adding or deleting
1958     * holdings, we shift the playing board accordingly
1959     * This kludge is needed because in ICS observe mode, we get boards
1960     * of an ongoing game without knowing the variant, and learn about the
1961     * latter only later. This can be because of the move list we requested,
1962     * in which case the game history is refilled from the beginning anyway,
1963     * but also when receiving holdings of a crazyhouse game. In the latter
1964     * case we want to add those holdings to the already received position.
1965     */
1966
1967    
1968    if (appData.debugMode) {
1969      fprintf(debugFP, "Switch board from %s to %s\n",
1970              VariantName(gameInfo.variant), VariantName(newVariant));
1971      setbuf(debugFP, NULL);
1972    }
1973    shuffleOpenings = 0;       /* [HGM] shuffle */
1974    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1975    switch(newVariant) 
1976      {
1977      case VariantShogi:
1978        newWidth = 9;  newHeight = 9;
1979        gameInfo.holdingsSize = 7;
1980      case VariantBughouse:
1981      case VariantCrazyhouse:
1982        newHoldingsWidth = 2; break;
1983      case VariantGreat:
1984        newWidth = 10;
1985      case VariantSuper:
1986        newHoldingsWidth = 2;
1987        gameInfo.holdingsSize = 8;
1988        break;
1989      case VariantGothic:
1990      case VariantCapablanca:
1991      case VariantCapaRandom:
1992        newWidth = 10;
1993      default:
1994        newHoldingsWidth = gameInfo.holdingsSize = 0;
1995      };
1996    
1997    if(newWidth  != gameInfo.boardWidth  ||
1998       newHeight != gameInfo.boardHeight ||
1999       newHoldingsWidth != gameInfo.holdingsWidth ) {
2000      
2001      /* shift position to new playing area, if needed */
2002      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++) 
2004          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007        for(i=0; i<newHeight; i++) {
2008          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2009          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2010        }
2011      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2012        for(i=0; i<BOARD_HEIGHT; i++)
2013          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2014            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015              board[i][j];
2016      }
2017      gameInfo.boardWidth  = newWidth;
2018      gameInfo.boardHeight = newHeight;
2019      gameInfo.holdingsWidth = newHoldingsWidth;
2020      gameInfo.variant = newVariant;
2021      InitDrawingSizes(-2, 0);
2022    } else gameInfo.variant = newVariant;
2023    CopyBoard(oldBoard, board);   // remember correctly formatted board
2024      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2025    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2026 }
2027
2028 static int loggedOn = FALSE;
2029
2030 /*-- Game start info cache: --*/
2031 int gs_gamenum;
2032 char gs_kind[MSG_SIZ];
2033 static char player1Name[128] = "";
2034 static char player2Name[128] = "";
2035 static char cont_seq[] = "\n\\   ";
2036 static int player1Rating = -1;
2037 static int player2Rating = -1;
2038 /*----------------------------*/
2039
2040 ColorClass curColor = ColorNormal;
2041 int suppressKibitz = 0;
2042
2043 void
2044 read_from_ics(isr, closure, data, count, error)
2045      InputSourceRef isr;
2046      VOIDSTAR closure;
2047      char *data;
2048      int count;
2049      int error;
2050 {
2051 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2052 #define STARTED_NONE 0
2053 #define STARTED_MOVES 1
2054 #define STARTED_BOARD 2
2055 #define STARTED_OBSERVE 3
2056 #define STARTED_HOLDINGS 4
2057 #define STARTED_CHATTER 5
2058 #define STARTED_COMMENT 6
2059 #define STARTED_MOVES_NOHIDE 7
2060     
2061     static int started = STARTED_NONE;
2062     static char parse[20000];
2063     static int parse_pos = 0;
2064     static char buf[BUF_SIZE + 1];
2065     static int firstTime = TRUE, intfSet = FALSE;
2066     static ColorClass prevColor = ColorNormal;
2067     static int savingComment = FALSE;
2068     static int cmatch = 0; // continuation sequence match
2069     char *bp;
2070     char str[500];
2071     int i, oldi;
2072     int buf_len;
2073     int next_out;
2074     int tkind;
2075     int backup;    /* [DM] For zippy color lines */
2076     char *p;
2077     char talker[MSG_SIZ]; // [HGM] chat
2078     int channel;
2079
2080     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2081
2082     if (appData.debugMode) {
2083       if (!error) {
2084         fprintf(debugFP, "<ICS: ");
2085         show_bytes(debugFP, data, count);
2086         fprintf(debugFP, "\n");
2087       }
2088     }
2089
2090     if (appData.debugMode) { int f = forwardMostMove;
2091         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2092                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2093     }
2094     if (count > 0) {
2095         /* If last read ended with a partial line that we couldn't parse,
2096            prepend it to the new read and try again. */
2097         if (leftover_len > 0) {
2098             for (i=0; i<leftover_len; i++)
2099               buf[i] = buf[leftover_start + i];
2100         }
2101
2102     /* copy new characters into the buffer */
2103     bp = buf + leftover_len;
2104     buf_len=leftover_len;
2105     for (i=0; i<count; i++)
2106     {
2107         // ignore these
2108         if (data[i] == '\r')
2109             continue;
2110
2111         // join lines split by ICS?
2112         if (!appData.noJoin)
2113         {
2114             /*
2115                 Joining just consists of finding matches against the
2116                 continuation sequence, and discarding that sequence
2117                 if found instead of copying it.  So, until a match
2118                 fails, there's nothing to do since it might be the
2119                 complete sequence, and thus, something we don't want
2120                 copied.
2121             */
2122             if (data[i] == cont_seq[cmatch])
2123             {
2124                 cmatch++;
2125                 if (cmatch == strlen(cont_seq))
2126                 {
2127                     cmatch = 0; // complete match.  just reset the counter
2128
2129                     /*
2130                         it's possible for the ICS to not include the space
2131                         at the end of the last word, making our [correct]
2132                         join operation fuse two separate words.  the server
2133                         does this when the space occurs at the width setting.
2134                     */
2135                     if (!buf_len || buf[buf_len-1] != ' ')
2136                     {
2137                         *bp++ = ' ';
2138                         buf_len++;
2139                     }
2140                 }
2141                 continue;
2142             }
2143             else if (cmatch)
2144             {
2145                 /*
2146                     match failed, so we have to copy what matched before
2147                     falling through and copying this character.  In reality,
2148                     this will only ever be just the newline character, but
2149                     it doesn't hurt to be precise.
2150                 */
2151                 strncpy(bp, cont_seq, cmatch);
2152                 bp += cmatch;
2153                 buf_len += cmatch;
2154                 cmatch = 0;
2155             }
2156         }
2157
2158         // copy this char
2159         *bp++ = data[i];
2160         buf_len++;
2161     }
2162
2163         buf[buf_len] = NULLCHAR;
2164         next_out = leftover_len;
2165         leftover_start = 0;
2166         
2167         i = 0;
2168         while (i < buf_len) {
2169             /* Deal with part of the TELNET option negotiation
2170                protocol.  We refuse to do anything beyond the
2171                defaults, except that we allow the WILL ECHO option,
2172                which ICS uses to turn off password echoing when we are
2173                directly connected to it.  We reject this option
2174                if localLineEditing mode is on (always on in xboard)
2175                and we are talking to port 23, which might be a real
2176                telnet server that will try to keep WILL ECHO on permanently.
2177              */
2178             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2179                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2180                 unsigned char option;
2181                 oldi = i;
2182                 switch ((unsigned char) buf[++i]) {
2183                   case TN_WILL:
2184                     if (appData.debugMode)
2185                       fprintf(debugFP, "\n<WILL ");
2186                     switch (option = (unsigned char) buf[++i]) {
2187                       case TN_ECHO:
2188                         if (appData.debugMode)
2189                           fprintf(debugFP, "ECHO ");
2190                         /* Reply only if this is a change, according
2191                            to the protocol rules. */
2192                         if (remoteEchoOption) break;
2193                         if (appData.localLineEditing &&
2194                             atoi(appData.icsPort) == TN_PORT) {
2195                             TelnetRequest(TN_DONT, TN_ECHO);
2196                         } else {
2197                             EchoOff();
2198                             TelnetRequest(TN_DO, TN_ECHO);
2199                             remoteEchoOption = TRUE;
2200                         }
2201                         break;
2202                       default:
2203                         if (appData.debugMode)
2204                           fprintf(debugFP, "%d ", option);
2205                         /* Whatever this is, we don't want it. */
2206                         TelnetRequest(TN_DONT, option);
2207                         break;
2208                     }
2209                     break;
2210                   case TN_WONT:
2211                     if (appData.debugMode)
2212                       fprintf(debugFP, "\n<WONT ");
2213                     switch (option = (unsigned char) buf[++i]) {
2214                       case TN_ECHO:
2215                         if (appData.debugMode)
2216                           fprintf(debugFP, "ECHO ");
2217                         /* Reply only if this is a change, according
2218                            to the protocol rules. */
2219                         if (!remoteEchoOption) break;
2220                         EchoOn();
2221                         TelnetRequest(TN_DONT, TN_ECHO);
2222                         remoteEchoOption = FALSE;
2223                         break;
2224                       default:
2225                         if (appData.debugMode)
2226                           fprintf(debugFP, "%d ", (unsigned char) option);
2227                         /* Whatever this is, it must already be turned
2228                            off, because we never agree to turn on
2229                            anything non-default, so according to the
2230                            protocol rules, we don't reply. */
2231                         break;
2232                     }
2233                     break;
2234                   case TN_DO:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<DO ");
2237                     switch (option = (unsigned char) buf[++i]) {
2238                       default:
2239                         /* Whatever this is, we refuse to do it. */
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "%d ", option);
2242                         TelnetRequest(TN_WONT, option);
2243                         break;
2244                     }
2245                     break;
2246                   case TN_DONT:
2247                     if (appData.debugMode)
2248                       fprintf(debugFP, "\n<DONT ");
2249                     switch (option = (unsigned char) buf[++i]) {
2250                       default:
2251                         if (appData.debugMode)
2252                           fprintf(debugFP, "%d ", option);
2253                         /* Whatever this is, we are already not doing
2254                            it, because we never agree to do anything
2255                            non-default, so according to the protocol
2256                            rules, we don't reply. */
2257                         break;
2258                     }
2259                     break;
2260                   case TN_IAC:
2261                     if (appData.debugMode)
2262                       fprintf(debugFP, "\n<IAC ");
2263                     /* Doubled IAC; pass it through */
2264                     i--;
2265                     break;
2266                   default:
2267                     if (appData.debugMode)
2268                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2269                     /* Drop all other telnet commands on the floor */
2270                     break;
2271                 }
2272                 if (oldi > next_out)
2273                   SendToPlayer(&buf[next_out], oldi - next_out);
2274                 if (++i > next_out)
2275                   next_out = i;
2276                 continue;
2277             }
2278                 
2279             /* OK, this at least will *usually* work */
2280             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2281                 loggedOn = TRUE;
2282             }
2283             
2284             if (loggedOn && !intfSet) {
2285                 if (ics_type == ICS_ICC) {
2286                   sprintf(str,
2287                           "/set-quietly interface %s\n/set-quietly style 12\n",
2288                           programVersion);
2289                 } else if (ics_type == ICS_CHESSNET) {
2290                   sprintf(str, "/style 12\n");
2291                 } else {
2292                   strcpy(str, "alias $ @\n$set interface ");
2293                   strcat(str, programVersion);
2294                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2295 #ifdef WIN32
2296                   strcat(str, "$iset nohighlight 1\n");
2297 #endif
2298                   strcat(str, "$iset lock 1\n$style 12\n");
2299                 }
2300                 SendToICS(str);
2301                 NotifyFrontendLogin();
2302                 intfSet = TRUE;
2303             }
2304
2305             if (started == STARTED_COMMENT) {
2306                 /* Accumulate characters in comment */
2307                 parse[parse_pos++] = buf[i];
2308                 if (buf[i] == '\n') {
2309                     parse[parse_pos] = NULLCHAR;
2310                     if(chattingPartner>=0) {
2311                         char mess[MSG_SIZ];
2312                         sprintf(mess, "%s%s", talker, parse);
2313                         OutputChatMessage(chattingPartner, mess);
2314                         chattingPartner = -1;
2315                     } else
2316                     if(!suppressKibitz) // [HGM] kibitz
2317                         AppendComment(forwardMostMove, StripHighlight(parse));
2318                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2319                         int nrDigit = 0, nrAlph = 0, i;
2320                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2321                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2322                         parse[parse_pos] = NULLCHAR;
2323                         // try to be smart: if it does not look like search info, it should go to
2324                         // ICS interaction window after all, not to engine-output window.
2325                         for(i=0; i<parse_pos; i++) { // count letters and digits
2326                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2327                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2328                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2329                         }
2330                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2331                             int depth=0; float score;
2332                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2333                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2334                                 pvInfoList[forwardMostMove-1].depth = depth;
2335                                 pvInfoList[forwardMostMove-1].score = 100*score;
2336                             }
2337                             OutputKibitz(suppressKibitz, parse);
2338                         } else {
2339                             char tmp[MSG_SIZ];
2340                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2341                             SendToPlayer(tmp, strlen(tmp));
2342                         }
2343                     }
2344                     started = STARTED_NONE;
2345                 } else {
2346                     /* Don't match patterns against characters in chatter */
2347                     i++;
2348                     continue;
2349                 }
2350             }
2351             if (started == STARTED_CHATTER) {
2352                 if (buf[i] != '\n') {
2353                     /* Don't match patterns against characters in chatter */
2354                     i++;
2355                     continue;
2356                 }
2357                 started = STARTED_NONE;
2358             }
2359
2360             /* Kludge to deal with rcmd protocol */
2361             if (firstTime && looking_at(buf, &i, "\001*")) {
2362                 DisplayFatalError(&buf[1], 0, 1);
2363                 continue;
2364             } else {
2365                 firstTime = FALSE;
2366             }
2367
2368             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2369                 ics_type = ICS_ICC;
2370                 ics_prefix = "/";
2371                 if (appData.debugMode)
2372                   fprintf(debugFP, "ics_type %d\n", ics_type);
2373                 continue;
2374             }
2375             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2376                 ics_type = ICS_FICS;
2377                 ics_prefix = "$";
2378                 if (appData.debugMode)
2379                   fprintf(debugFP, "ics_type %d\n", ics_type);
2380                 continue;
2381             }
2382             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2383                 ics_type = ICS_CHESSNET;
2384                 ics_prefix = "/";
2385                 if (appData.debugMode)
2386                   fprintf(debugFP, "ics_type %d\n", ics_type);
2387                 continue;
2388             }
2389
2390             if (!loggedOn &&
2391                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2392                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2393                  looking_at(buf, &i, "will be \"*\""))) {
2394               strcpy(ics_handle, star_match[0]);
2395               continue;
2396             }
2397
2398             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2399               char buf[MSG_SIZ];
2400               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2401               DisplayIcsInteractionTitle(buf);
2402               have_set_title = TRUE;
2403             }
2404
2405             /* skip finger notes */
2406             if (started == STARTED_NONE &&
2407                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2408                  (buf[i] == '1' && buf[i+1] == '0')) &&
2409                 buf[i+2] == ':' && buf[i+3] == ' ') {
2410               started = STARTED_CHATTER;
2411               i += 3;
2412               continue;
2413             }
2414
2415             /* skip formula vars */
2416             if (started == STARTED_NONE &&
2417                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2418               started = STARTED_CHATTER;
2419               i += 3;
2420               continue;
2421             }
2422
2423             oldi = i;
2424             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2425             if (appData.autoKibitz && started == STARTED_NONE && 
2426                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2427                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2428                 if(looking_at(buf, &i, "* kibitzes: ") &&
2429                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2430                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2431                         suppressKibitz = TRUE;
2432                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2433                                 && (gameMode == IcsPlayingWhite)) ||
2434                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2435                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2436                             started = STARTED_CHATTER; // own kibitz we simply discard
2437                         else {
2438                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2439                             parse_pos = 0; parse[0] = NULLCHAR;
2440                             savingComment = TRUE;
2441                             suppressKibitz = gameMode != IcsObserving ? 2 :
2442                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2443                         } 
2444                         continue;
2445                 } else
2446                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2447                     started = STARTED_CHATTER;
2448                     suppressKibitz = TRUE;
2449                 }
2450             } // [HGM] kibitz: end of patch
2451
2452 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2453
2454             // [HGM] chat: intercept tells by users for which we have an open chat window
2455             channel = -1;
2456             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2457                                            looking_at(buf, &i, "* whispers:") ||
2458                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2459                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2460                 int p;
2461                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2462                 chattingPartner = -1;
2463
2464                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2465                 for(p=0; p<MAX_CHAT; p++) {
2466                     if(channel == atoi(chatPartner[p])) {
2467                     talker[0] = '['; strcat(talker, "]");
2468                     chattingPartner = p; break;
2469                     }
2470                 } else
2471                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2472                 for(p=0; p<MAX_CHAT; p++) {
2473                     if(!strcmp("WHISPER", chatPartner[p])) {
2474                         talker[0] = '['; strcat(talker, "]");
2475                         chattingPartner = p; break;
2476                     }
2477                 }
2478                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2479                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2480                     talker[0] = 0;
2481                     chattingPartner = p; break;
2482                 }
2483                 if(chattingPartner<0) i = oldi; else {
2484                     started = STARTED_COMMENT;
2485                     parse_pos = 0; parse[0] = NULLCHAR;
2486                     savingComment = TRUE;
2487                     suppressKibitz = TRUE;
2488                 }
2489             } // [HGM] chat: end of patch
2490
2491             if (appData.zippyTalk || appData.zippyPlay) {
2492                 /* [DM] Backup address for color zippy lines */
2493                 backup = i;
2494 #if ZIPPY
2495        #ifdef WIN32
2496                if (loggedOn == TRUE)
2497                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2498                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2499        #else
2500                 if (ZippyControl(buf, &i) ||
2501                     ZippyConverse(buf, &i) ||
2502                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2503                       loggedOn = TRUE;
2504                       if (!appData.colorize) continue;
2505                 }
2506        #endif
2507 #endif
2508             } // [DM] 'else { ' deleted
2509                 if (
2510                     /* Regular tells and says */
2511                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2512                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2513                     looking_at(buf, &i, "* says: ") ||
2514                     /* Don't color "message" or "messages" output */
2515                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2516                     looking_at(buf, &i, "*. * at *:*: ") ||
2517                     looking_at(buf, &i, "--* (*:*): ") ||
2518                     /* Message notifications (same color as tells) */
2519                     looking_at(buf, &i, "* has left a message ") ||
2520                     looking_at(buf, &i, "* just sent you a message:\n") ||
2521                     /* Whispers and kibitzes */
2522                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2523                     looking_at(buf, &i, "* kibitzes: ") ||
2524                     /* Channel tells */
2525                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2526
2527                   if (tkind == 1 && strchr(star_match[0], ':')) {
2528                       /* Avoid "tells you:" spoofs in channels */
2529                      tkind = 3;
2530                   }
2531                   if (star_match[0][0] == NULLCHAR ||
2532                       strchr(star_match[0], ' ') ||
2533                       (tkind == 3 && strchr(star_match[1], ' '))) {
2534                     /* Reject bogus matches */
2535                     i = oldi;
2536                   } else {
2537                     if (appData.colorize) {
2538                       if (oldi > next_out) {
2539                         SendToPlayer(&buf[next_out], oldi - next_out);
2540                         next_out = oldi;
2541                       }
2542                       switch (tkind) {
2543                       case 1:
2544                         Colorize(ColorTell, FALSE);
2545                         curColor = ColorTell;
2546                         break;
2547                       case 2:
2548                         Colorize(ColorKibitz, FALSE);
2549                         curColor = ColorKibitz;
2550                         break;
2551                       case 3:
2552                         p = strrchr(star_match[1], '(');
2553                         if (p == NULL) {
2554                           p = star_match[1];
2555                         } else {
2556                           p++;
2557                         }
2558                         if (atoi(p) == 1) {
2559                           Colorize(ColorChannel1, FALSE);
2560                           curColor = ColorChannel1;
2561                         } else {
2562                           Colorize(ColorChannel, FALSE);
2563                           curColor = ColorChannel;
2564                         }
2565                         break;
2566                       case 5:
2567                         curColor = ColorNormal;
2568                         break;
2569                       }
2570                     }
2571                     if (started == STARTED_NONE && appData.autoComment &&
2572                         (gameMode == IcsObserving ||
2573                          gameMode == IcsPlayingWhite ||
2574                          gameMode == IcsPlayingBlack)) {
2575                       parse_pos = i - oldi;
2576                       memcpy(parse, &buf[oldi], parse_pos);
2577                       parse[parse_pos] = NULLCHAR;
2578                       started = STARTED_COMMENT;
2579                       savingComment = TRUE;
2580                     } else {
2581                       started = STARTED_CHATTER;
2582                       savingComment = FALSE;
2583                     }
2584                     loggedOn = TRUE;
2585                     continue;
2586                   }
2587                 }
2588
2589                 if (looking_at(buf, &i, "* s-shouts: ") ||
2590                     looking_at(buf, &i, "* c-shouts: ")) {
2591                     if (appData.colorize) {
2592                         if (oldi > next_out) {
2593                             SendToPlayer(&buf[next_out], oldi - next_out);
2594                             next_out = oldi;
2595                         }
2596                         Colorize(ColorSShout, FALSE);
2597                         curColor = ColorSShout;
2598                     }
2599                     loggedOn = TRUE;
2600                     started = STARTED_CHATTER;
2601                     continue;
2602                 }
2603
2604                 if (looking_at(buf, &i, "--->")) {
2605                     loggedOn = TRUE;
2606                     continue;
2607                 }
2608
2609                 if (looking_at(buf, &i, "* shouts: ") ||
2610                     looking_at(buf, &i, "--> ")) {
2611                     if (appData.colorize) {
2612                         if (oldi > next_out) {
2613                             SendToPlayer(&buf[next_out], oldi - next_out);
2614                             next_out = oldi;
2615                         }
2616                         Colorize(ColorShout, FALSE);
2617                         curColor = ColorShout;
2618                     }
2619                     loggedOn = TRUE;
2620                     started = STARTED_CHATTER;
2621                     continue;
2622                 }
2623
2624                 if (looking_at( buf, &i, "Challenge:")) {
2625                     if (appData.colorize) {
2626                         if (oldi > next_out) {
2627                             SendToPlayer(&buf[next_out], oldi - next_out);
2628                             next_out = oldi;
2629                         }
2630                         Colorize(ColorChallenge, FALSE);
2631                         curColor = ColorChallenge;
2632                     }
2633                     loggedOn = TRUE;
2634                     continue;
2635                 }
2636
2637                 if (looking_at(buf, &i, "* offers you") ||
2638                     looking_at(buf, &i, "* offers to be") ||
2639                     looking_at(buf, &i, "* would like to") ||
2640                     looking_at(buf, &i, "* requests to") ||
2641                     looking_at(buf, &i, "Your opponent offers") ||
2642                     looking_at(buf, &i, "Your opponent requests")) {
2643
2644                     if (appData.colorize) {
2645                         if (oldi > next_out) {
2646                             SendToPlayer(&buf[next_out], oldi - next_out);
2647                             next_out = oldi;
2648                         }
2649                         Colorize(ColorRequest, FALSE);
2650                         curColor = ColorRequest;
2651                     }
2652                     continue;
2653                 }
2654
2655                 if (looking_at(buf, &i, "* (*) seeking")) {
2656                     if (appData.colorize) {
2657                         if (oldi > next_out) {
2658                             SendToPlayer(&buf[next_out], oldi - next_out);
2659                             next_out = oldi;
2660                         }
2661                         Colorize(ColorSeek, FALSE);
2662                         curColor = ColorSeek;
2663                     }
2664                     continue;
2665             }
2666
2667             if (looking_at(buf, &i, "\\   ")) {
2668                 if (prevColor != ColorNormal) {
2669                     if (oldi > next_out) {
2670                         SendToPlayer(&buf[next_out], oldi - next_out);
2671                         next_out = oldi;
2672                     }
2673                     Colorize(prevColor, TRUE);
2674                     curColor = prevColor;
2675                 }
2676                 if (savingComment) {
2677                     parse_pos = i - oldi;
2678                     memcpy(parse, &buf[oldi], parse_pos);
2679                     parse[parse_pos] = NULLCHAR;
2680                     started = STARTED_COMMENT;
2681                 } else {
2682                     started = STARTED_CHATTER;
2683                 }
2684                 continue;
2685             }
2686
2687             if (looking_at(buf, &i, "Black Strength :") ||
2688                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2689                 looking_at(buf, &i, "<10>") ||
2690                 looking_at(buf, &i, "#@#")) {
2691                 /* Wrong board style */
2692                 loggedOn = TRUE;
2693                 SendToICS(ics_prefix);
2694                 SendToICS("set style 12\n");
2695                 SendToICS(ics_prefix);
2696                 SendToICS("refresh\n");
2697                 continue;
2698             }
2699             
2700             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2701                 ICSInitScript();
2702                 have_sent_ICS_logon = 1;
2703                 continue;
2704             }
2705               
2706             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2707                 (looking_at(buf, &i, "\n<12> ") ||
2708                  looking_at(buf, &i, "<12> "))) {
2709                 loggedOn = TRUE;
2710                 if (oldi > next_out) {
2711                     SendToPlayer(&buf[next_out], oldi - next_out);
2712                 }
2713                 next_out = i;
2714                 started = STARTED_BOARD;
2715                 parse_pos = 0;
2716                 continue;
2717             }
2718
2719             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2720                 looking_at(buf, &i, "<b1> ")) {
2721                 if (oldi > next_out) {
2722                     SendToPlayer(&buf[next_out], oldi - next_out);
2723                 }
2724                 next_out = i;
2725                 started = STARTED_HOLDINGS;
2726                 parse_pos = 0;
2727                 continue;
2728             }
2729
2730             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2731                 loggedOn = TRUE;
2732                 /* Header for a move list -- first line */
2733
2734                 switch (ics_getting_history) {
2735                   case H_FALSE:
2736                     switch (gameMode) {
2737                       case IcsIdle:
2738                       case BeginningOfGame:
2739                         /* User typed "moves" or "oldmoves" while we
2740                            were idle.  Pretend we asked for these
2741                            moves and soak them up so user can step
2742                            through them and/or save them.
2743                            */
2744                         Reset(FALSE, TRUE);
2745                         gameMode = IcsObserving;
2746                         ModeHighlight();
2747                         ics_gamenum = -1;
2748                         ics_getting_history = H_GOT_UNREQ_HEADER;
2749                         break;
2750                       case EditGame: /*?*/
2751                       case EditPosition: /*?*/
2752                         /* Should above feature work in these modes too? */
2753                         /* For now it doesn't */
2754                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2755                         break;
2756                       default:
2757                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2758                         break;
2759                     }
2760                     break;
2761                   case H_REQUESTED:
2762                     /* Is this the right one? */
2763                     if (gameInfo.white && gameInfo.black &&
2764                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2765                         strcmp(gameInfo.black, star_match[2]) == 0) {
2766                         /* All is well */
2767                         ics_getting_history = H_GOT_REQ_HEADER;
2768                     }
2769                     break;
2770                   case H_GOT_REQ_HEADER:
2771                   case H_GOT_UNREQ_HEADER:
2772                   case H_GOT_UNWANTED_HEADER:
2773                   case H_GETTING_MOVES:
2774                     /* Should not happen */
2775                     DisplayError(_("Error gathering move list: two headers"), 0);
2776                     ics_getting_history = H_FALSE;
2777                     break;
2778                 }
2779
2780                 /* Save player ratings into gameInfo if needed */
2781                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2782                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2783                     (gameInfo.whiteRating == -1 ||
2784                      gameInfo.blackRating == -1)) {
2785
2786                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2787                     gameInfo.blackRating = string_to_rating(star_match[3]);
2788                     if (appData.debugMode)
2789                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2790                               gameInfo.whiteRating, gameInfo.blackRating);
2791                 }
2792                 continue;
2793             }
2794
2795             if (looking_at(buf, &i,
2796               "* * match, initial time: * minute*, increment: * second")) {
2797                 /* Header for a move list -- second line */
2798                 /* Initial board will follow if this is a wild game */
2799                 if (gameInfo.event != NULL) free(gameInfo.event);
2800                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2801                 gameInfo.event = StrSave(str);
2802                 /* [HGM] we switched variant. Translate boards if needed. */
2803                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2804                 continue;
2805             }
2806
2807             if (looking_at(buf, &i, "Move  ")) {
2808                 /* Beginning of a move list */
2809                 switch (ics_getting_history) {
2810                   case H_FALSE:
2811                     /* Normally should not happen */
2812                     /* Maybe user hit reset while we were parsing */
2813                     break;
2814                   case H_REQUESTED:
2815                     /* Happens if we are ignoring a move list that is not
2816                      * the one we just requested.  Common if the user
2817                      * tries to observe two games without turning off
2818                      * getMoveList */
2819                     break;
2820                   case H_GETTING_MOVES:
2821                     /* Should not happen */
2822                     DisplayError(_("Error gathering move list: nested"), 0);
2823                     ics_getting_history = H_FALSE;
2824                     break;
2825                   case H_GOT_REQ_HEADER:
2826                     ics_getting_history = H_GETTING_MOVES;
2827                     started = STARTED_MOVES;
2828                     parse_pos = 0;
2829                     if (oldi > next_out) {
2830                         SendToPlayer(&buf[next_out], oldi - next_out);
2831                     }
2832                     break;
2833                   case H_GOT_UNREQ_HEADER:
2834                     ics_getting_history = H_GETTING_MOVES;
2835                     started = STARTED_MOVES_NOHIDE;
2836                     parse_pos = 0;
2837                     break;
2838                   case H_GOT_UNWANTED_HEADER:
2839                     ics_getting_history = H_FALSE;
2840                     break;
2841                 }
2842                 continue;
2843             }                           
2844             
2845             if (looking_at(buf, &i, "% ") ||
2846                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2847                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2848                 savingComment = FALSE;
2849                 switch (started) {
2850                   case STARTED_MOVES:
2851                   case STARTED_MOVES_NOHIDE:
2852                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2853                     parse[parse_pos + i - oldi] = NULLCHAR;
2854                     ParseGameHistory(parse);
2855 #if ZIPPY
2856                     if (appData.zippyPlay && first.initDone) {
2857                         FeedMovesToProgram(&first, forwardMostMove);
2858                         if (gameMode == IcsPlayingWhite) {
2859                             if (WhiteOnMove(forwardMostMove)) {
2860                                 if (first.sendTime) {
2861                                   if (first.useColors) {
2862                                     SendToProgram("black\n", &first); 
2863                                   }
2864                                   SendTimeRemaining(&first, TRUE);
2865                                 }
2866                                 if (first.useColors) {
2867                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2868                                 }
2869                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2870                                 first.maybeThinking = TRUE;
2871                             } else {
2872                                 if (first.usePlayother) {
2873                                   if (first.sendTime) {
2874                                     SendTimeRemaining(&first, TRUE);
2875                                   }
2876                                   SendToProgram("playother\n", &first);
2877                                   firstMove = FALSE;
2878                                 } else {
2879                                   firstMove = TRUE;
2880                                 }
2881                             }
2882                         } else if (gameMode == IcsPlayingBlack) {
2883                             if (!WhiteOnMove(forwardMostMove)) {
2884                                 if (first.sendTime) {
2885                                   if (first.useColors) {
2886                                     SendToProgram("white\n", &first);
2887                                   }
2888                                   SendTimeRemaining(&first, FALSE);
2889                                 }
2890                                 if (first.useColors) {
2891                                   SendToProgram("black\n", &first);
2892                                 }
2893                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2894                                 first.maybeThinking = TRUE;
2895                             } else {
2896                                 if (first.usePlayother) {
2897                                   if (first.sendTime) {
2898                                     SendTimeRemaining(&first, FALSE);
2899                                   }
2900                                   SendToProgram("playother\n", &first);
2901                                   firstMove = FALSE;
2902                                 } else {
2903                                   firstMove = TRUE;
2904                                 }
2905                             }
2906                         }                       
2907                     }
2908 #endif
2909                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2910                         /* Moves came from oldmoves or moves command
2911                            while we weren't doing anything else.
2912                            */
2913                         currentMove = forwardMostMove;
2914                         ClearHighlights();/*!!could figure this out*/
2915                         flipView = appData.flipView;
2916                         DrawPosition(TRUE, boards[currentMove]);
2917                         DisplayBothClocks();
2918                         sprintf(str, "%s vs. %s",
2919                                 gameInfo.white, gameInfo.black);
2920                         DisplayTitle(str);
2921                         gameMode = IcsIdle;
2922                     } else {
2923                         /* Moves were history of an active game */
2924                         if (gameInfo.resultDetails != NULL) {
2925                             free(gameInfo.resultDetails);
2926                             gameInfo.resultDetails = NULL;
2927                         }
2928                     }
2929                     HistorySet(parseList, backwardMostMove,
2930                                forwardMostMove, currentMove-1);
2931                     DisplayMove(currentMove - 1);
2932                     if (started == STARTED_MOVES) next_out = i;
2933                     started = STARTED_NONE;
2934                     ics_getting_history = H_FALSE;
2935                     break;
2936
2937                   case STARTED_OBSERVE:
2938                     started = STARTED_NONE;
2939                     SendToICS(ics_prefix);
2940                     SendToICS("refresh\n");
2941                     break;
2942
2943                   default:
2944                     break;
2945                 }
2946                 if(bookHit) { // [HGM] book: simulate book reply
2947                     static char bookMove[MSG_SIZ]; // a bit generous?
2948
2949                     programStats.nodes = programStats.depth = programStats.time = 
2950                     programStats.score = programStats.got_only_move = 0;
2951                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2952
2953                     strcpy(bookMove, "move ");
2954                     strcat(bookMove, bookHit);
2955                     HandleMachineMove(bookMove, &first);
2956                 }
2957                 continue;
2958             }
2959             
2960             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2961                  started == STARTED_HOLDINGS ||
2962                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2963                 /* Accumulate characters in move list or board */
2964                 parse[parse_pos++] = buf[i];
2965             }
2966             
2967             /* Start of game messages.  Mostly we detect start of game
2968                when the first board image arrives.  On some versions
2969                of the ICS, though, we need to do a "refresh" after starting
2970                to observe in order to get the current board right away. */
2971             if (looking_at(buf, &i, "Adding game * to observation list")) {
2972                 started = STARTED_OBSERVE;
2973                 continue;
2974             }
2975
2976             /* Handle auto-observe */
2977             if (appData.autoObserve &&
2978                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2979                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2980                 char *player;
2981                 /* Choose the player that was highlighted, if any. */
2982                 if (star_match[0][0] == '\033' ||
2983                     star_match[1][0] != '\033') {
2984                     player = star_match[0];
2985                 } else {
2986                     player = star_match[2];
2987                 }
2988                 sprintf(str, "%sobserve %s\n",
2989                         ics_prefix, StripHighlightAndTitle(player));
2990                 SendToICS(str);
2991
2992                 /* Save ratings from notify string */
2993                 strcpy(player1Name, star_match[0]);
2994                 player1Rating = string_to_rating(star_match[1]);
2995                 strcpy(player2Name, star_match[2]);
2996                 player2Rating = string_to_rating(star_match[3]);
2997
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, 
3000                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3001                           player1Name, player1Rating,
3002                           player2Name, player2Rating);
3003
3004                 continue;
3005             }
3006
3007             /* Deal with automatic examine mode after a game,
3008                and with IcsObserving -> IcsExamining transition */
3009             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3010                 looking_at(buf, &i, "has made you an examiner of game *")) {
3011
3012                 int gamenum = atoi(star_match[0]);
3013                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3014                     gamenum == ics_gamenum) {
3015                     /* We were already playing or observing this game;
3016                        no need to refetch history */
3017                     gameMode = IcsExamining;
3018                     if (pausing) {
3019                         pauseExamForwardMostMove = forwardMostMove;
3020                     } else if (currentMove < forwardMostMove) {
3021                         ForwardInner(forwardMostMove);
3022                     }
3023                 } else {
3024                     /* I don't think this case really can happen */
3025                     SendToICS(ics_prefix);
3026                     SendToICS("refresh\n");
3027                 }
3028                 continue;
3029             }    
3030             
3031             /* Error messages */
3032 //          if (ics_user_moved) {
3033             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3034                 if (looking_at(buf, &i, "Illegal move") ||
3035                     looking_at(buf, &i, "Not a legal move") ||
3036                     looking_at(buf, &i, "Your king is in check") ||
3037                     looking_at(buf, &i, "It isn't your turn") ||
3038                     looking_at(buf, &i, "It is not your move")) {
3039                     /* Illegal move */
3040                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3041                         currentMove = --forwardMostMove;
3042                         DisplayMove(currentMove - 1); /* before DMError */
3043                         DrawPosition(FALSE, boards[currentMove]);
3044                         SwitchClocks();
3045                         DisplayBothClocks();
3046                     }
3047                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3048                     ics_user_moved = 0;
3049                     continue;
3050                 }
3051             }
3052
3053             if (looking_at(buf, &i, "still have time") ||
3054                 looking_at(buf, &i, "not out of time") ||
3055                 looking_at(buf, &i, "either player is out of time") ||
3056                 looking_at(buf, &i, "has timeseal; checking")) {
3057                 /* We must have called his flag a little too soon */
3058                 whiteFlag = blackFlag = FALSE;
3059                 continue;
3060             }
3061
3062             if (looking_at(buf, &i, "added * seconds to") ||
3063                 looking_at(buf, &i, "seconds were added to")) {
3064                 /* Update the clocks */
3065                 SendToICS(ics_prefix);
3066                 SendToICS("refresh\n");
3067                 continue;
3068             }
3069
3070             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3071                 ics_clock_paused = TRUE;
3072                 StopClocks();
3073                 continue;
3074             }
3075
3076             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3077                 ics_clock_paused = FALSE;
3078                 StartClocks();
3079                 continue;
3080             }
3081
3082             /* Grab player ratings from the Creating: message.
3083                Note we have to check for the special case when
3084                the ICS inserts things like [white] or [black]. */
3085             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3086                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3087                 /* star_matches:
3088                    0    player 1 name (not necessarily white)
3089                    1    player 1 rating
3090                    2    empty, white, or black (IGNORED)
3091                    3    player 2 name (not necessarily black)
3092                    4    player 2 rating
3093                    
3094                    The names/ratings are sorted out when the game
3095                    actually starts (below).
3096                 */
3097                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3098                 player1Rating = string_to_rating(star_match[1]);
3099                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3100                 player2Rating = string_to_rating(star_match[4]);
3101
3102                 if (appData.debugMode)
3103                   fprintf(debugFP, 
3104                           "Ratings from 'Creating:' %s %d, %s %d\n",
3105                           player1Name, player1Rating,
3106                           player2Name, player2Rating);
3107
3108                 continue;
3109             }
3110             
3111             /* Improved generic start/end-of-game messages */
3112             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3113                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3114                 /* If tkind == 0: */
3115                 /* star_match[0] is the game number */
3116                 /*           [1] is the white player's name */
3117                 /*           [2] is the black player's name */
3118                 /* For end-of-game: */
3119                 /*           [3] is the reason for the game end */
3120                 /*           [4] is a PGN end game-token, preceded by " " */
3121                 /* For start-of-game: */
3122                 /*           [3] begins with "Creating" or "Continuing" */
3123                 /*           [4] is " *" or empty (don't care). */
3124                 int gamenum = atoi(star_match[0]);
3125                 char *whitename, *blackname, *why, *endtoken;
3126                 ChessMove endtype = (ChessMove) 0;
3127
3128                 if (tkind == 0) {
3129                   whitename = star_match[1];
3130                   blackname = star_match[2];
3131                   why = star_match[3];
3132                   endtoken = star_match[4];
3133                 } else {
3134                   whitename = star_match[1];
3135                   blackname = star_match[3];
3136                   why = star_match[5];
3137                   endtoken = star_match[6];
3138                 }
3139
3140                 /* Game start messages */
3141                 if (strncmp(why, "Creating ", 9) == 0 ||
3142                     strncmp(why, "Continuing ", 11) == 0) {
3143                     gs_gamenum = gamenum;
3144                     strcpy(gs_kind, strchr(why, ' ') + 1);
3145                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3146 #if ZIPPY
3147                     if (appData.zippyPlay) {
3148                         ZippyGameStart(whitename, blackname);
3149                     }
3150 #endif /*ZIPPY*/
3151                     continue;
3152                 }
3153
3154                 /* Game end messages */
3155                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3156                     ics_gamenum != gamenum) {
3157                     continue;
3158                 }
3159                 while (endtoken[0] == ' ') endtoken++;
3160                 switch (endtoken[0]) {
3161                   case '*':
3162                   default:
3163                     endtype = GameUnfinished;
3164                     break;
3165                   case '0':
3166                     endtype = BlackWins;
3167                     break;
3168                   case '1':
3169                     if (endtoken[1] == '/')
3170                       endtype = GameIsDrawn;
3171                     else
3172                       endtype = WhiteWins;
3173                     break;
3174                 }
3175                 GameEnds(endtype, why, GE_ICS);
3176 #if ZIPPY
3177                 if (appData.zippyPlay && first.initDone) {
3178                     ZippyGameEnd(endtype, why);
3179                     if (first.pr == NULL) {
3180                       /* Start the next process early so that we'll
3181                          be ready for the next challenge */
3182                       StartChessProgram(&first);
3183                     }
3184                     /* Send "new" early, in case this command takes
3185                        a long time to finish, so that we'll be ready
3186                        for the next challenge. */
3187                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3188                     Reset(TRUE, TRUE);
3189                 }
3190 #endif /*ZIPPY*/
3191                 continue;
3192             }
3193
3194             if (looking_at(buf, &i, "Removing game * from observation") ||
3195                 looking_at(buf, &i, "no longer observing game *") ||
3196                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3197                 if (gameMode == IcsObserving &&
3198                     atoi(star_match[0]) == ics_gamenum)
3199                   {
3200                       /* icsEngineAnalyze */
3201                       if (appData.icsEngineAnalyze) {
3202                             ExitAnalyzeMode();
3203                             ModeHighlight();
3204                       }
3205                       StopClocks();
3206                       gameMode = IcsIdle;
3207                       ics_gamenum = -1;
3208                       ics_user_moved = FALSE;
3209                   }
3210                 continue;
3211             }
3212
3213             if (looking_at(buf, &i, "no longer examining game *")) {
3214                 if (gameMode == IcsExamining &&
3215                     atoi(star_match[0]) == ics_gamenum)
3216                   {
3217                       gameMode = IcsIdle;
3218                       ics_gamenum = -1;
3219                       ics_user_moved = FALSE;
3220                   }
3221                 continue;
3222             }
3223
3224             /* Advance leftover_start past any newlines we find,
3225                so only partial lines can get reparsed */
3226             if (looking_at(buf, &i, "\n")) {
3227                 prevColor = curColor;
3228                 if (curColor != ColorNormal) {
3229                     if (oldi > next_out) {
3230                         SendToPlayer(&buf[next_out], oldi - next_out);
3231                         next_out = oldi;
3232                     }
3233                     Colorize(ColorNormal, FALSE);
3234                     curColor = ColorNormal;
3235                 }
3236                 if (started == STARTED_BOARD) {
3237                     started = STARTED_NONE;
3238                     parse[parse_pos] = NULLCHAR;
3239                     ParseBoard12(parse);
3240                     ics_user_moved = 0;
3241
3242                     /* Send premove here */
3243                     if (appData.premove) {
3244                       char str[MSG_SIZ];
3245                       if (currentMove == 0 &&
3246                           gameMode == IcsPlayingWhite &&
3247                           appData.premoveWhite) {
3248                         sprintf(str, "%s\n", appData.premoveWhiteText);
3249                         if (appData.debugMode)
3250                           fprintf(debugFP, "Sending premove:\n");
3251                         SendToICS(str);
3252                       } else if (currentMove == 1 &&
3253                                  gameMode == IcsPlayingBlack &&
3254                                  appData.premoveBlack) {
3255                         sprintf(str, "%s\n", appData.premoveBlackText);
3256                         if (appData.debugMode)
3257                           fprintf(debugFP, "Sending premove:\n");
3258                         SendToICS(str);
3259                       } else if (gotPremove) {
3260                         gotPremove = 0;
3261                         ClearPremoveHighlights();
3262                         if (appData.debugMode)
3263                           fprintf(debugFP, "Sending premove:\n");
3264                           UserMoveEvent(premoveFromX, premoveFromY, 
3265                                         premoveToX, premoveToY, 
3266                                         premovePromoChar);
3267                       }
3268                     }
3269
3270                     /* Usually suppress following prompt */
3271                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3272                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3273                         if (looking_at(buf, &i, "*% ")) {
3274                             savingComment = FALSE;
3275                         }
3276                     }
3277                     next_out = i;
3278                 } else if (started == STARTED_HOLDINGS) {
3279                     int gamenum;
3280                     char new_piece[MSG_SIZ];
3281                     started = STARTED_NONE;
3282                     parse[parse_pos] = NULLCHAR;
3283                     if (appData.debugMode)
3284                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3285                                                         parse, currentMove);
3286                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3287                         gamenum == ics_gamenum) {
3288                         if (gameInfo.variant == VariantNormal) {
3289                           /* [HGM] We seem to switch variant during a game!
3290                            * Presumably no holdings were displayed, so we have
3291                            * to move the position two files to the right to
3292                            * create room for them!
3293                            */
3294                           VariantClass newVariant;
3295                           switch(gameInfo.boardWidth) { // base guess on board width
3296                                 case 9:  newVariant = VariantShogi; break;
3297                                 case 10: newVariant = VariantGreat; break;
3298                                 default: newVariant = VariantCrazyhouse; break;
3299                           }
3300                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3301                           /* Get a move list just to see the header, which
3302                              will tell us whether this is really bug or zh */
3303                           if (ics_getting_history == H_FALSE) {
3304                             ics_getting_history = H_REQUESTED;
3305                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3306                             SendToICS(str);
3307                           }
3308                         }
3309                         new_piece[0] = NULLCHAR;
3310                         sscanf(parse, "game %d white [%s black [%s <- %s",
3311                                &gamenum, white_holding, black_holding,
3312                                new_piece);
3313                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3314                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3315                         /* [HGM] copy holdings to board holdings area */
3316                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3317                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3318                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3319 #if ZIPPY
3320                         if (appData.zippyPlay && first.initDone) {
3321                             ZippyHoldings(white_holding, black_holding,
3322                                           new_piece);
3323                         }
3324 #endif /*ZIPPY*/
3325                         if (tinyLayout || smallLayout) {
3326                             char wh[16], bh[16];
3327                             PackHolding(wh, white_holding);
3328                             PackHolding(bh, black_holding);
3329                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3330                                     gameInfo.white, gameInfo.black);
3331                         } else {
3332                             sprintf(str, "%s [%s] vs. %s [%s]",
3333                                     gameInfo.white, white_holding,
3334                                     gameInfo.black, black_holding);
3335                         }
3336
3337                         DrawPosition(FALSE, boards[currentMove]);
3338                         DisplayTitle(str);
3339                     }
3340                     /* Suppress following prompt */
3341                     if (looking_at(buf, &i, "*% ")) {
3342                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3343                         savingComment = FALSE;
3344                     }
3345                     next_out = i;
3346                 }
3347                 continue;
3348             }
3349
3350             i++;                /* skip unparsed character and loop back */
3351         }
3352         
3353         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3354             started != STARTED_HOLDINGS && i > next_out) {
3355             SendToPlayer(&buf[next_out], i - next_out);
3356             next_out = i;
3357         }
3358         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3359         
3360         leftover_len = buf_len - leftover_start;
3361         /* if buffer ends with something we couldn't parse,
3362            reparse it after appending the next read */
3363         
3364     } else if (count == 0) {
3365         RemoveInputSource(isr);
3366         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3367     } else {
3368         DisplayFatalError(_("Error reading from ICS"), error, 1);
3369     }
3370 }
3371
3372
3373 /* Board style 12 looks like this:
3374    
3375    <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
3376    
3377  * The "<12> " is stripped before it gets to this routine.  The two
3378  * trailing 0's (flip state and clock ticking) are later addition, and
3379  * some chess servers may not have them, or may have only the first.
3380  * Additional trailing fields may be added in the future.  
3381  */
3382
3383 #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"
3384
3385 #define RELATION_OBSERVING_PLAYED    0
3386 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3387 #define RELATION_PLAYING_MYMOVE      1
3388 #define RELATION_PLAYING_NOTMYMOVE  -1
3389 #define RELATION_EXAMINING           2
3390 #define RELATION_ISOLATED_BOARD     -3
3391 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3392
3393 void
3394 ParseBoard12(string)
3395      char *string;
3396
3397     GameMode newGameMode;
3398     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3399     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3400     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3401     char to_play, board_chars[200];
3402     char move_str[500], str[500], elapsed_time[500];
3403     char black[32], white[32];
3404     Board board;
3405     int prevMove = currentMove;
3406     int ticking = 2;
3407     ChessMove moveType;
3408     int fromX, fromY, toX, toY;
3409     char promoChar;
3410     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3411     char *bookHit = NULL; // [HGM] book
3412     Boolean weird = FALSE, reqFlag = FALSE;
3413
3414     fromX = fromY = toX = toY = -1;
3415     
3416     newGame = FALSE;
3417
3418     if (appData.debugMode)
3419       fprintf(debugFP, _("Parsing board: %s\n"), string);
3420
3421     move_str[0] = NULLCHAR;
3422     elapsed_time[0] = NULLCHAR;
3423     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3424         int  i = 0, j;
3425         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3426             if(string[i] == ' ') { ranks++; files = 0; }
3427             else files++;
3428             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3429             i++;
3430         }
3431         for(j = 0; j <i; j++) board_chars[j] = string[j];
3432         board_chars[i] = '\0';
3433         string += i + 1;
3434     }
3435     n = sscanf(string, PATTERN, &to_play, &double_push,
3436                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3437                &gamenum, white, black, &relation, &basetime, &increment,
3438                &white_stren, &black_stren, &white_time, &black_time,
3439                &moveNum, str, elapsed_time, move_str, &ics_flip,
3440                &ticking);
3441
3442     if (n < 21) {
3443         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3444         DisplayError(str, 0);
3445         return;
3446     }
3447
3448     /* Convert the move number to internal form */
3449     moveNum = (moveNum - 1) * 2;
3450     if (to_play == 'B') moveNum++;
3451     if (moveNum >= MAX_MOVES) {
3452       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3453                         0, 1);
3454       return;
3455     }
3456     
3457     switch (relation) {
3458       case RELATION_OBSERVING_PLAYED:
3459       case RELATION_OBSERVING_STATIC:
3460         if (gamenum == -1) {
3461             /* Old ICC buglet */
3462             relation = RELATION_OBSERVING_STATIC;
3463         }
3464         newGameMode = IcsObserving;
3465         break;
3466       case RELATION_PLAYING_MYMOVE:
3467       case RELATION_PLAYING_NOTMYMOVE:
3468         newGameMode =
3469           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3470             IcsPlayingWhite : IcsPlayingBlack;
3471         break;
3472       case RELATION_EXAMINING:
3473         newGameMode = IcsExamining;
3474         break;
3475       case RELATION_ISOLATED_BOARD:
3476       default:
3477         /* Just display this board.  If user was doing something else,
3478            we will forget about it until the next board comes. */ 
3479         newGameMode = IcsIdle;
3480         break;
3481       case RELATION_STARTING_POSITION:
3482         newGameMode = gameMode;
3483         break;
3484     }
3485     
3486     /* Modify behavior for initial board display on move listing
3487        of wild games.
3488        */
3489     switch (ics_getting_history) {
3490       case H_FALSE:
3491       case H_REQUESTED:
3492         break;
3493       case H_GOT_REQ_HEADER:
3494       case H_GOT_UNREQ_HEADER:
3495         /* This is the initial position of the current game */
3496         gamenum = ics_gamenum;
3497         moveNum = 0;            /* old ICS bug workaround */
3498         if (to_play == 'B') {
3499           startedFromSetupPosition = TRUE;
3500           blackPlaysFirst = TRUE;
3501           moveNum = 1;
3502           if (forwardMostMove == 0) forwardMostMove = 1;
3503           if (backwardMostMove == 0) backwardMostMove = 1;
3504           if (currentMove == 0) currentMove = 1;
3505         }
3506         newGameMode = gameMode;
3507         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3508         break;
3509       case H_GOT_UNWANTED_HEADER:
3510         /* This is an initial board that we don't want */
3511         return;
3512       case H_GETTING_MOVES:
3513         /* Should not happen */
3514         DisplayError(_("Error gathering move list: extra board"), 0);
3515         ics_getting_history = H_FALSE;
3516         return;
3517     }
3518
3519    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3520                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3521      /* [HGM] We seem to have switched variant unexpectedly
3522       * Try to guess new variant from board size
3523       */
3524           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3525           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3526           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3527           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3528           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3529           if(!weird) newVariant = VariantNormal;
3530           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3531           /* Get a move list just to see the header, which
3532              will tell us whether this is really bug or zh */
3533           if (ics_getting_history == H_FALSE) {
3534             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3535             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3536             SendToICS(str);
3537           }
3538     }
3539     
3540     /* Take action if this is the first board of a new game, or of a
3541        different game than is currently being displayed.  */
3542     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3543         relation == RELATION_ISOLATED_BOARD) {
3544         
3545         /* Forget the old game and get the history (if any) of the new one */
3546         if (gameMode != BeginningOfGame) {
3547           Reset(TRUE, TRUE);
3548         }
3549         newGame = TRUE;
3550         if (appData.autoRaiseBoard) BoardToTop();
3551         prevMove = -3;
3552         if (gamenum == -1) {
3553             newGameMode = IcsIdle;
3554         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3555                    appData.getMoveList && !reqFlag) {
3556             /* Need to get game history */
3557             ics_getting_history = H_REQUESTED;
3558             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3559             SendToICS(str);
3560         }
3561         
3562         /* Initially flip the board to have black on the bottom if playing
3563            black or if the ICS flip flag is set, but let the user change
3564            it with the Flip View button. */
3565         flipView = appData.autoFlipView ? 
3566           (newGameMode == IcsPlayingBlack) || ics_flip :
3567           appData.flipView;
3568         
3569         /* Done with values from previous mode; copy in new ones */
3570         gameMode = newGameMode;
3571         ModeHighlight();
3572         ics_gamenum = gamenum;
3573         if (gamenum == gs_gamenum) {
3574             int klen = strlen(gs_kind);
3575             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3576             sprintf(str, "ICS %s", gs_kind);
3577             gameInfo.event = StrSave(str);
3578         } else {
3579             gameInfo.event = StrSave("ICS game");
3580         }
3581         gameInfo.site = StrSave(appData.icsHost);
3582         gameInfo.date = PGNDate();
3583         gameInfo.round = StrSave("-");
3584         gameInfo.white = StrSave(white);
3585         gameInfo.black = StrSave(black);
3586         timeControl = basetime * 60 * 1000;
3587         timeControl_2 = 0;
3588         timeIncrement = increment * 1000;
3589         movesPerSession = 0;
3590         gameInfo.timeControl = TimeControlTagValue();
3591         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3592   if (appData.debugMode) {
3593     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3594     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3595     setbuf(debugFP, NULL);
3596   }
3597
3598         gameInfo.outOfBook = NULL;
3599         
3600         /* Do we have the ratings? */
3601         if (strcmp(player1Name, white) == 0 &&
3602             strcmp(player2Name, black) == 0) {
3603             if (appData.debugMode)
3604               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3605                       player1Rating, player2Rating);
3606             gameInfo.whiteRating = player1Rating;
3607             gameInfo.blackRating = player2Rating;
3608         } else if (strcmp(player2Name, white) == 0 &&
3609                    strcmp(player1Name, black) == 0) {
3610             if (appData.debugMode)
3611               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3612                       player2Rating, player1Rating);
3613             gameInfo.whiteRating = player2Rating;
3614             gameInfo.blackRating = player1Rating;
3615         }
3616         player1Name[0] = player2Name[0] = NULLCHAR;
3617
3618         /* Silence shouts if requested */
3619         if (appData.quietPlay &&
3620             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3621             SendToICS(ics_prefix);
3622             SendToICS("set shout 0\n");
3623         }
3624     }
3625     
3626     /* Deal with midgame name changes */
3627     if (!newGame) {
3628         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3629             if (gameInfo.white) free(gameInfo.white);
3630             gameInfo.white = StrSave(white);
3631         }
3632         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3633             if (gameInfo.black) free(gameInfo.black);
3634             gameInfo.black = StrSave(black);
3635         }
3636     }
3637     
3638     /* Throw away game result if anything actually changes in examine mode */
3639     if (gameMode == IcsExamining && !newGame) {
3640         gameInfo.result = GameUnfinished;
3641         if (gameInfo.resultDetails != NULL) {
3642             free(gameInfo.resultDetails);
3643             gameInfo.resultDetails = NULL;
3644         }
3645     }
3646     
3647     /* In pausing && IcsExamining mode, we ignore boards coming
3648        in if they are in a different variation than we are. */
3649     if (pauseExamInvalid) return;
3650     if (pausing && gameMode == IcsExamining) {
3651         if (moveNum <= pauseExamForwardMostMove) {
3652             pauseExamInvalid = TRUE;
3653             forwardMostMove = pauseExamForwardMostMove;
3654             return;
3655         }
3656     }
3657     
3658   if (appData.debugMode) {
3659     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3660   }
3661     /* Parse the board */
3662     for (k = 0; k < ranks; k++) {
3663       for (j = 0; j < files; j++)
3664         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3665       if(gameInfo.holdingsWidth > 1) {
3666            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3667            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3668       }
3669     }
3670     CopyBoard(boards[moveNum], board);
3671     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3672     if (moveNum == 0) {
3673         startedFromSetupPosition =
3674           !CompareBoards(board, initialPosition);
3675         if(startedFromSetupPosition)
3676             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3677     }
3678
3679     /* [HGM] Set castling rights. Take the outermost Rooks,
3680        to make it also work for FRC opening positions. Note that board12
3681        is really defective for later FRC positions, as it has no way to
3682        indicate which Rook can castle if they are on the same side of King.
3683        For the initial position we grant rights to the outermost Rooks,
3684        and remember thos rights, and we then copy them on positions
3685        later in an FRC game. This means WB might not recognize castlings with
3686        Rooks that have moved back to their original position as illegal,
3687        but in ICS mode that is not its job anyway.
3688     */
3689     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3690     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3691
3692         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3693             if(board[0][i] == WhiteRook) j = i;
3694         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3695         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3696             if(board[0][i] == WhiteRook) j = i;
3697         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3698         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3699             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3700         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3701         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3702             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3704
3705         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3706         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3707             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3708         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709             if(board[BOARD_HEIGHT-1][k] == bKing)
3710                 initialRights[5] = castlingRights[moveNum][5] = k;
3711         if(gameInfo.variant == VariantTwoKings) {
3712             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3713             if(board[0][4] == wKing) initialRights[2] = castlingRights[moveNum][2] = 4;
3714             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = castlingRights[moveNum][5] = 4;
3715         }
3716     } else { int r;
3717         r = castlingRights[moveNum][0] = initialRights[0];
3718         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3719         r = castlingRights[moveNum][1] = initialRights[1];
3720         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3721         r = castlingRights[moveNum][3] = initialRights[3];
3722         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3723         r = castlingRights[moveNum][4] = initialRights[4];
3724         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3725         /* wildcastle kludge: always assume King has rights */
3726         r = castlingRights[moveNum][2] = initialRights[2];
3727         r = castlingRights[moveNum][5] = initialRights[5];
3728     }
3729     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3730     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3731
3732     
3733     if (ics_getting_history == H_GOT_REQ_HEADER ||
3734         ics_getting_history == H_GOT_UNREQ_HEADER) {
3735         /* This was an initial position from a move list, not
3736            the current position */
3737         return;
3738     }
3739     
3740     /* Update currentMove and known move number limits */
3741     newMove = newGame || moveNum > forwardMostMove;
3742
3743     if (newGame) {
3744         forwardMostMove = backwardMostMove = currentMove = moveNum;
3745         if (gameMode == IcsExamining && moveNum == 0) {
3746           /* Workaround for ICS limitation: we are not told the wild
3747              type when starting to examine a game.  But if we ask for
3748              the move list, the move list header will tell us */
3749             ics_getting_history = H_REQUESTED;
3750             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3751             SendToICS(str);
3752         }
3753     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3754                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3755 #if ZIPPY
3756         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3757         /* [HGM] applied this also to an engine that is silently watching        */
3758         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3759             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3760             gameInfo.variant == currentlyInitializedVariant) {
3761           takeback = forwardMostMove - moveNum;
3762           for (i = 0; i < takeback; i++) {
3763             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3764             SendToProgram("undo\n", &first);
3765           }
3766         }
3767 #endif
3768
3769         forwardMostMove = moveNum;
3770         if (!pausing || currentMove > forwardMostMove)
3771           currentMove = forwardMostMove;
3772     } else {
3773         /* New part of history that is not contiguous with old part */ 
3774         if (pausing && gameMode == IcsExamining) {
3775             pauseExamInvalid = TRUE;
3776             forwardMostMove = pauseExamForwardMostMove;
3777             return;
3778         }
3779         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3780 #if ZIPPY
3781             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3782                 // [HGM] when we will receive the move list we now request, it will be
3783                 // fed to the engine from the first move on. So if the engine is not
3784                 // in the initial position now, bring it there.
3785                 InitChessProgram(&first, 0);
3786             }
3787 #endif
3788             ics_getting_history = H_REQUESTED;
3789             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3790             SendToICS(str);
3791         }
3792         forwardMostMove = backwardMostMove = currentMove = moveNum;
3793     }
3794     
3795     /* Update the clocks */
3796     if (strchr(elapsed_time, '.')) {
3797       /* Time is in ms */
3798       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3799       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3800     } else {
3801       /* Time is in seconds */
3802       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3803       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3804     }
3805       
3806
3807 #if ZIPPY
3808     if (appData.zippyPlay && newGame &&
3809         gameMode != IcsObserving && gameMode != IcsIdle &&
3810         gameMode != IcsExamining)
3811       ZippyFirstBoard(moveNum, basetime, increment);
3812 #endif
3813     
3814     /* Put the move on the move list, first converting
3815        to canonical algebraic form. */
3816     if (moveNum > 0) {
3817   if (appData.debugMode) {
3818     if (appData.debugMode) { int f = forwardMostMove;
3819         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3820                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3821     }
3822     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3823     fprintf(debugFP, "moveNum = %d\n", moveNum);
3824     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3825     setbuf(debugFP, NULL);
3826   }
3827         if (moveNum <= backwardMostMove) {
3828             /* We don't know what the board looked like before
3829                this move.  Punt. */
3830             strcpy(parseList[moveNum - 1], move_str);
3831             strcat(parseList[moveNum - 1], " ");
3832             strcat(parseList[moveNum - 1], elapsed_time);
3833             moveList[moveNum - 1][0] = NULLCHAR;
3834         } else if (strcmp(move_str, "none") == 0) {
3835             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3836             /* Again, we don't know what the board looked like;
3837                this is really the start of the game. */
3838             parseList[moveNum - 1][0] = NULLCHAR;
3839             moveList[moveNum - 1][0] = NULLCHAR;
3840             backwardMostMove = moveNum;
3841             startedFromSetupPosition = TRUE;
3842             fromX = fromY = toX = toY = -1;
3843         } else {
3844           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3845           //                 So we parse the long-algebraic move string in stead of the SAN move
3846           int valid; char buf[MSG_SIZ], *prom;
3847
3848           // str looks something like "Q/a1-a2"; kill the slash
3849           if(str[1] == '/') 
3850                 sprintf(buf, "%c%s", str[0], str+2);
3851           else  strcpy(buf, str); // might be castling
3852           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3853                 strcat(buf, prom); // long move lacks promo specification!
3854           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3855                 if(appData.debugMode) 
3856                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3857                 strcpy(move_str, buf);
3858           }
3859           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3860                                 &fromX, &fromY, &toX, &toY, &promoChar)
3861                || ParseOneMove(buf, moveNum - 1, &moveType,
3862                                 &fromX, &fromY, &toX, &toY, &promoChar);
3863           // end of long SAN patch
3864           if (valid) {
3865             (void) CoordsToAlgebraic(boards[moveNum - 1],
3866                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3867                                      fromY, fromX, toY, toX, promoChar,
3868                                      parseList[moveNum-1]);
3869             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3870                              castlingRights[moveNum]) ) {
3871               case MT_NONE:
3872               case MT_STALEMATE:
3873               default:
3874                 break;
3875               case MT_CHECK:
3876                 if(gameInfo.variant != VariantShogi)
3877                     strcat(parseList[moveNum - 1], "+");
3878                 break;
3879               case MT_CHECKMATE:
3880               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3881                 strcat(parseList[moveNum - 1], "#");
3882                 break;
3883             }
3884             strcat(parseList[moveNum - 1], " ");
3885             strcat(parseList[moveNum - 1], elapsed_time);
3886             /* currentMoveString is set as a side-effect of ParseOneMove */
3887             strcpy(moveList[moveNum - 1], currentMoveString);
3888             strcat(moveList[moveNum - 1], "\n");
3889           } else {
3890             /* Move from ICS was illegal!?  Punt. */
3891   if (appData.debugMode) {
3892     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3893     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3894   }
3895             strcpy(parseList[moveNum - 1], move_str);
3896             strcat(parseList[moveNum - 1], " ");
3897             strcat(parseList[moveNum - 1], elapsed_time);
3898             moveList[moveNum - 1][0] = NULLCHAR;
3899             fromX = fromY = toX = toY = -1;
3900           }
3901         }
3902   if (appData.debugMode) {
3903     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3904     setbuf(debugFP, NULL);
3905   }
3906
3907 #if ZIPPY
3908         /* Send move to chess program (BEFORE animating it). */
3909         if (appData.zippyPlay && !newGame && newMove && 
3910            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3911
3912             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3913                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3914                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3915                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3916                             move_str);
3917                     DisplayError(str, 0);
3918                 } else {
3919                     if (first.sendTime) {
3920                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3921                     }
3922                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3923                     if (firstMove && !bookHit) {
3924                         firstMove = FALSE;
3925                         if (first.useColors) {
3926                           SendToProgram(gameMode == IcsPlayingWhite ?
3927                                         "white\ngo\n" :
3928                                         "black\ngo\n", &first);
3929                         } else {
3930                           SendToProgram("go\n", &first);
3931                         }
3932                         first.maybeThinking = TRUE;
3933                     }
3934                 }
3935             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3936               if (moveList[moveNum - 1][0] == NULLCHAR) {
3937                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3938                 DisplayError(str, 0);
3939               } else {
3940                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3941                 SendMoveToProgram(moveNum - 1, &first);
3942               }
3943             }
3944         }
3945 #endif
3946     }
3947
3948     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3949         /* If move comes from a remote source, animate it.  If it
3950            isn't remote, it will have already been animated. */
3951         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3952             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3953         }
3954         if (!pausing && appData.highlightLastMove) {
3955             SetHighlights(fromX, fromY, toX, toY);
3956         }
3957     }
3958     
3959     /* Start the clocks */
3960     whiteFlag = blackFlag = FALSE;
3961     appData.clockMode = !(basetime == 0 && increment == 0);
3962     if (ticking == 0) {
3963       ics_clock_paused = TRUE;
3964       StopClocks();
3965     } else if (ticking == 1) {
3966       ics_clock_paused = FALSE;
3967     }
3968     if (gameMode == IcsIdle ||
3969         relation == RELATION_OBSERVING_STATIC ||
3970         relation == RELATION_EXAMINING ||
3971         ics_clock_paused)
3972       DisplayBothClocks();
3973     else
3974       StartClocks();
3975     
3976     /* Display opponents and material strengths */
3977     if (gameInfo.variant != VariantBughouse &&
3978         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3979         if (tinyLayout || smallLayout) {
3980             if(gameInfo.variant == VariantNormal)
3981                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3982                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3983                     basetime, increment);
3984             else
3985                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3986                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3987                     basetime, increment, (int) gameInfo.variant);
3988         } else {
3989             if(gameInfo.variant == VariantNormal)
3990                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3991                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3992                     basetime, increment);
3993             else
3994                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3995                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3996                     basetime, increment, VariantName(gameInfo.variant));
3997         }
3998         DisplayTitle(str);
3999   if (appData.debugMode) {
4000     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4001   }
4002     }
4003
4004    
4005     /* Display the board */
4006     if (!pausing && !appData.noGUI) {
4007       
4008       if (appData.premove)
4009           if (!gotPremove || 
4010              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4011              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4012               ClearPremoveHighlights();
4013
4014       DrawPosition(FALSE, boards[currentMove]);
4015       DisplayMove(moveNum - 1);
4016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4020       }
4021     }
4022
4023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4024 #if ZIPPY
4025     if(bookHit) { // [HGM] book: simulate book reply
4026         static char bookMove[MSG_SIZ]; // a bit generous?
4027
4028         programStats.nodes = programStats.depth = programStats.time = 
4029         programStats.score = programStats.got_only_move = 0;
4030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4031
4032         strcpy(bookMove, "move ");
4033         strcat(bookMove, bookHit);
4034         HandleMachineMove(bookMove, &first);
4035     }
4036 #endif
4037 }
4038
4039 void
4040 GetMoveListEvent()
4041 {
4042     char buf[MSG_SIZ];
4043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4044         ics_getting_history = H_REQUESTED;
4045         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4046         SendToICS(buf);
4047     }
4048 }
4049
4050 void
4051 AnalysisPeriodicEvent(force)
4052      int force;
4053 {
4054     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4055          && !force) || !appData.periodicUpdates)
4056       return;
4057
4058     /* Send . command to Crafty to collect stats */
4059     SendToProgram(".\n", &first);
4060
4061     /* Don't send another until we get a response (this makes
4062        us stop sending to old Crafty's which don't understand
4063        the "." command (sending illegal cmds resets node count & time,
4064        which looks bad)) */
4065     programStats.ok_to_send = 0;
4066 }
4067
4068 void ics_update_width(new_width)
4069         int new_width;
4070 {
4071         ics_printf("set width %d\n", new_width);
4072 }
4073
4074 void
4075 SendMoveToProgram(moveNum, cps)
4076      int moveNum;
4077      ChessProgramState *cps;
4078 {
4079     char buf[MSG_SIZ];
4080
4081     if (cps->useUsermove) {
4082       SendToProgram("usermove ", cps);
4083     }
4084     if (cps->useSAN) {
4085       char *space;
4086       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4087         int len = space - parseList[moveNum];
4088         memcpy(buf, parseList[moveNum], len);
4089         buf[len++] = '\n';
4090         buf[len] = NULLCHAR;
4091       } else {
4092         sprintf(buf, "%s\n", parseList[moveNum]);
4093       }
4094       SendToProgram(buf, cps);
4095     } else {
4096       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4097         AlphaRank(moveList[moveNum], 4);
4098         SendToProgram(moveList[moveNum], cps);
4099         AlphaRank(moveList[moveNum], 4); // and back
4100       } else
4101       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4102        * the engine. It would be nice to have a better way to identify castle 
4103        * moves here. */
4104       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4105                                                                          && cps->useOOCastle) {
4106         int fromX = moveList[moveNum][0] - AAA; 
4107         int fromY = moveList[moveNum][1] - ONE;
4108         int toX = moveList[moveNum][2] - AAA; 
4109         int toY = moveList[moveNum][3] - ONE;
4110         if((boards[moveNum][fromY][fromX] == WhiteKing 
4111             && boards[moveNum][toY][toX] == WhiteRook)
4112            || (boards[moveNum][fromY][fromX] == BlackKing 
4113                && boards[moveNum][toY][toX] == BlackRook)) {
4114           if(toX > fromX) SendToProgram("O-O\n", cps);
4115           else SendToProgram("O-O-O\n", cps);
4116         }
4117         else SendToProgram(moveList[moveNum], cps);
4118       }
4119       else SendToProgram(moveList[moveNum], cps);
4120       /* End of additions by Tord */
4121     }
4122
4123     /* [HGM] setting up the opening has brought engine in force mode! */
4124     /*       Send 'go' if we are in a mode where machine should play. */
4125     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4126         (gameMode == TwoMachinesPlay   ||
4127 #ifdef ZIPPY
4128          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4129 #endif
4130          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4131         SendToProgram("go\n", cps);
4132   if (appData.debugMode) {
4133     fprintf(debugFP, "(extra)\n");
4134   }
4135     }
4136     setboardSpoiledMachineBlack = 0;
4137 }
4138
4139 void
4140 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4141      ChessMove moveType;
4142      int fromX, fromY, toX, toY;
4143 {
4144     char user_move[MSG_SIZ];
4145
4146     switch (moveType) {
4147       default:
4148         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4149                 (int)moveType, fromX, fromY, toX, toY);
4150         DisplayError(user_move + strlen("say "), 0);
4151         break;
4152       case WhiteKingSideCastle:
4153       case BlackKingSideCastle:
4154       case WhiteQueenSideCastleWild:
4155       case BlackQueenSideCastleWild:
4156       /* PUSH Fabien */
4157       case WhiteHSideCastleFR:
4158       case BlackHSideCastleFR:
4159       /* POP Fabien */
4160         sprintf(user_move, "o-o\n");
4161         break;
4162       case WhiteQueenSideCastle:
4163       case BlackQueenSideCastle:
4164       case WhiteKingSideCastleWild:
4165       case BlackKingSideCastleWild:
4166       /* PUSH Fabien */
4167       case WhiteASideCastleFR:
4168       case BlackASideCastleFR:
4169       /* POP Fabien */
4170         sprintf(user_move, "o-o-o\n");
4171         break;
4172       case WhitePromotionQueen:
4173       case BlackPromotionQueen:
4174       case WhitePromotionRook:
4175       case BlackPromotionRook:
4176       case WhitePromotionBishop:
4177       case BlackPromotionBishop:
4178       case WhitePromotionKnight:
4179       case BlackPromotionKnight:
4180       case WhitePromotionKing:
4181       case BlackPromotionKing:
4182       case WhitePromotionChancellor:
4183       case BlackPromotionChancellor:
4184       case WhitePromotionArchbishop:
4185       case BlackPromotionArchbishop:
4186         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4187             sprintf(user_move, "%c%c%c%c=%c\n",
4188                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189                 PieceToChar(WhiteFerz));
4190         else if(gameInfo.variant == VariantGreat)
4191             sprintf(user_move, "%c%c%c%c=%c\n",
4192                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193                 PieceToChar(WhiteMan));
4194         else
4195             sprintf(user_move, "%c%c%c%c=%c\n",
4196                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4197                 PieceToChar(PromoPiece(moveType)));
4198         break;
4199       case WhiteDrop:
4200       case BlackDrop:
4201         sprintf(user_move, "%c@%c%c\n",
4202                 ToUpper(PieceToChar((ChessSquare) fromX)),
4203                 AAA + toX, ONE + toY);
4204         break;
4205       case NormalMove:
4206       case WhiteCapturesEnPassant:
4207       case BlackCapturesEnPassant:
4208       case IllegalMove:  /* could be a variant we don't quite understand */
4209         sprintf(user_move, "%c%c%c%c\n",
4210                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4211         break;
4212     }
4213     SendToICS(user_move);
4214     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4215         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4216 }
4217
4218 void
4219 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4220      int rf, ff, rt, ft;
4221      char promoChar;
4222      char move[7];
4223 {
4224     if (rf == DROP_RANK) {
4225         sprintf(move, "%c@%c%c\n",
4226                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4227     } else {
4228         if (promoChar == 'x' || promoChar == NULLCHAR) {
4229             sprintf(move, "%c%c%c%c\n",
4230                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4231         } else {
4232             sprintf(move, "%c%c%c%c%c\n",
4233                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4234         }
4235     }
4236 }
4237
4238 void
4239 ProcessICSInitScript(f)
4240      FILE *f;
4241 {
4242     char buf[MSG_SIZ];
4243
4244     while (fgets(buf, MSG_SIZ, f)) {
4245         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4246     }
4247
4248     fclose(f);
4249 }
4250
4251
4252 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4253 void
4254 AlphaRank(char *move, int n)
4255 {
4256 //    char *p = move, c; int x, y;
4257
4258     if (appData.debugMode) {
4259         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4260     }
4261
4262     if(move[1]=='*' && 
4263        move[2]>='0' && move[2]<='9' &&
4264        move[3]>='a' && move[3]<='x'    ) {
4265         move[1] = '@';
4266         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4267         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4268     } else
4269     if(move[0]>='0' && move[0]<='9' &&
4270        move[1]>='a' && move[1]<='x' &&
4271        move[2]>='0' && move[2]<='9' &&
4272        move[3]>='a' && move[3]<='x'    ) {
4273         /* input move, Shogi -> normal */
4274         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4275         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4276         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4277         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278     } else
4279     if(move[1]=='@' &&
4280        move[3]>='0' && move[3]<='9' &&
4281        move[2]>='a' && move[2]<='x'    ) {
4282         move[1] = '*';
4283         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4284         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4285     } else
4286     if(
4287        move[0]>='a' && move[0]<='x' &&
4288        move[3]>='0' && move[3]<='9' &&
4289        move[2]>='a' && move[2]<='x'    ) {
4290          /* output move, normal -> Shogi */
4291         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4292         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4293         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4295         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4296     }
4297     if (appData.debugMode) {
4298         fprintf(debugFP, "   out = '%s'\n", move);
4299     }
4300 }
4301
4302 /* Parser for moves from gnuchess, ICS, or user typein box */
4303 Boolean
4304 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4305      char *move;
4306      int moveNum;
4307      ChessMove *moveType;
4308      int *fromX, *fromY, *toX, *toY;
4309      char *promoChar;
4310 {       
4311     if (appData.debugMode) {
4312         fprintf(debugFP, "move to parse: %s\n", move);
4313     }
4314     *moveType = yylexstr(moveNum, move);
4315
4316     switch (*moveType) {
4317       case WhitePromotionChancellor:
4318       case BlackPromotionChancellor:
4319       case WhitePromotionArchbishop:
4320       case BlackPromotionArchbishop:
4321       case WhitePromotionQueen:
4322       case BlackPromotionQueen:
4323       case WhitePromotionRook:
4324       case BlackPromotionRook:
4325       case WhitePromotionBishop:
4326       case BlackPromotionBishop:
4327       case WhitePromotionKnight:
4328       case BlackPromotionKnight:
4329       case WhitePromotionKing:
4330       case BlackPromotionKing:
4331       case NormalMove:
4332       case WhiteCapturesEnPassant:
4333       case BlackCapturesEnPassant:
4334       case WhiteKingSideCastle:
4335       case WhiteQueenSideCastle:
4336       case BlackKingSideCastle:
4337       case BlackQueenSideCastle:
4338       case WhiteKingSideCastleWild:
4339       case WhiteQueenSideCastleWild:
4340       case BlackKingSideCastleWild:
4341       case BlackQueenSideCastleWild:
4342       /* Code added by Tord: */
4343       case WhiteHSideCastleFR:
4344       case WhiteASideCastleFR:
4345       case BlackHSideCastleFR:
4346       case BlackASideCastleFR:
4347       /* End of code added by Tord */
4348       case IllegalMove:         /* bug or odd chess variant */
4349         *fromX = currentMoveString[0] - AAA;
4350         *fromY = currentMoveString[1] - ONE;
4351         *toX = currentMoveString[2] - AAA;
4352         *toY = currentMoveString[3] - ONE;
4353         *promoChar = currentMoveString[4];
4354         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4355             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4356     if (appData.debugMode) {
4357         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4358     }
4359             *fromX = *fromY = *toX = *toY = 0;
4360             return FALSE;
4361         }
4362         if (appData.testLegality) {
4363           return (*moveType != IllegalMove);
4364         } else {
4365           return !(*fromX == *toX && *fromY == *toY);
4366         }
4367
4368       case WhiteDrop:
4369       case BlackDrop:
4370         *fromX = *moveType == WhiteDrop ?
4371           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4372           (int) CharToPiece(ToLower(currentMoveString[0]));
4373         *fromY = DROP_RANK;
4374         *toX = currentMoveString[2] - AAA;
4375         *toY = currentMoveString[3] - ONE;
4376         *promoChar = NULLCHAR;
4377         return TRUE;
4378
4379       case AmbiguousMove:
4380       case ImpossibleMove:
4381       case (ChessMove) 0:       /* end of file */
4382       case ElapsedTime:
4383       case Comment:
4384       case PGNTag:
4385       case NAG:
4386       case WhiteWins:
4387       case BlackWins:
4388       case GameIsDrawn:
4389       default:
4390     if (appData.debugMode) {
4391         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4392     }
4393         /* bug? */
4394         *fromX = *fromY = *toX = *toY = 0;
4395         *promoChar = NULLCHAR;
4396         return FALSE;
4397     }
4398 }
4399
4400 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4401 // All positions will have equal probability, but the current method will not provide a unique
4402 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4403 #define DARK 1
4404 #define LITE 2
4405 #define ANY 3
4406
4407 int squaresLeft[4];
4408 int piecesLeft[(int)BlackPawn];
4409 int seed, nrOfShuffles;
4410
4411 void GetPositionNumber()
4412 {       // sets global variable seed
4413         int i;
4414
4415         seed = appData.defaultFrcPosition;
4416         if(seed < 0) { // randomize based on time for negative FRC position numbers
4417                 for(i=0; i<50; i++) seed += random();
4418                 seed = random() ^ random() >> 8 ^ random() << 8;
4419                 if(seed<0) seed = -seed;
4420         }
4421 }
4422
4423 int put(Board board, int pieceType, int rank, int n, int shade)
4424 // put the piece on the (n-1)-th empty squares of the given shade
4425 {
4426         int i;
4427
4428         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4429                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4430                         board[rank][i] = (ChessSquare) pieceType;
4431                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4432                         squaresLeft[ANY]--;
4433                         piecesLeft[pieceType]--; 
4434                         return i;
4435                 }
4436         }
4437         return -1;
4438 }
4439
4440
4441 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4442 // calculate where the next piece goes, (any empty square), and put it there
4443 {
4444         int i;
4445
4446         i = seed % squaresLeft[shade];
4447         nrOfShuffles *= squaresLeft[shade];
4448         seed /= squaresLeft[shade];
4449         put(board, pieceType, rank, i, shade);
4450 }
4451
4452 void AddTwoPieces(Board board, int pieceType, int rank)
4453 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4454 {
4455         int i, n=squaresLeft[ANY], j=n-1, k;
4456
4457         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4458         i = seed % k;  // pick one
4459         nrOfShuffles *= k;
4460         seed /= k;
4461         while(i >= j) i -= j--;
4462         j = n - 1 - j; i += j;
4463         put(board, pieceType, rank, j, ANY);
4464         put(board, pieceType, rank, i, ANY);
4465 }
4466
4467 void SetUpShuffle(Board board, int number)
4468 {
4469         int i, p, first=1;
4470
4471         GetPositionNumber(); nrOfShuffles = 1;
4472
4473         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4474         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4475         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4476
4477         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4478
4479         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4480             p = (int) board[0][i];
4481             if(p < (int) BlackPawn) piecesLeft[p] ++;
4482             board[0][i] = EmptySquare;
4483         }
4484
4485         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4486             // shuffles restricted to allow normal castling put KRR first
4487             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4488                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4489             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4490                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4491             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4492                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4493             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4494                 put(board, WhiteRook, 0, 0, ANY);
4495             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4496         }
4497
4498         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4499             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4500             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4501                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4502                 while(piecesLeft[p] >= 2) {
4503                     AddOnePiece(board, p, 0, LITE);
4504                     AddOnePiece(board, p, 0, DARK);
4505                 }
4506                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4507             }
4508
4509         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4510             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4511             // but we leave King and Rooks for last, to possibly obey FRC restriction
4512             if(p == (int)WhiteRook) continue;
4513             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4514             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4515         }
4516
4517         // now everything is placed, except perhaps King (Unicorn) and Rooks
4518
4519         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4520             // Last King gets castling rights
4521             while(piecesLeft[(int)WhiteUnicorn]) {
4522                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4523                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4524             }
4525
4526             while(piecesLeft[(int)WhiteKing]) {
4527                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4528                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4529             }
4530
4531
4532         } else {
4533             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4534             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4535         }
4536
4537         // Only Rooks can be left; simply place them all
4538         while(piecesLeft[(int)WhiteRook]) {
4539                 i = put(board, WhiteRook, 0, 0, ANY);
4540                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4541                         if(first) {
4542                                 first=0;
4543                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4544                         }
4545                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4546                 }
4547         }
4548         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4549             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4550         }
4551
4552         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4553 }
4554
4555 int SetCharTable( char *table, const char * map )
4556 /* [HGM] moved here from winboard.c because of its general usefulness */
4557 /*       Basically a safe strcpy that uses the last character as King */
4558 {
4559     int result = FALSE; int NrPieces;
4560
4561     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4562                     && NrPieces >= 12 && !(NrPieces&1)) {
4563         int i; /* [HGM] Accept even length from 12 to 34 */
4564
4565         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4566         for( i=0; i<NrPieces/2-1; i++ ) {
4567             table[i] = map[i];
4568             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4569         }
4570         table[(int) WhiteKing]  = map[NrPieces/2-1];
4571         table[(int) BlackKing]  = map[NrPieces-1];
4572
4573         result = TRUE;
4574     }
4575
4576     return result;
4577 }
4578
4579 void Prelude(Board board)
4580 {       // [HGM] superchess: random selection of exo-pieces
4581         int i, j, k; ChessSquare p; 
4582         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4583
4584         GetPositionNumber(); // use FRC position number
4585
4586         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4587             SetCharTable(pieceToChar, appData.pieceToCharTable);
4588             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4589                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4590         }
4591
4592         j = seed%4;                 seed /= 4; 
4593         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4595         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4596         j = seed%3 + (seed%3 >= j); seed /= 3; 
4597         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4598         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4599         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4600         j = seed%3;                 seed /= 3; 
4601         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4603         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4604         j = seed%2 + (seed%2 >= j); seed /= 2; 
4605         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4606         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4607         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4608         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4609         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4610         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4611         put(board, exoPieces[0],    0, 0, ANY);
4612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4613 }
4614
4615 void
4616 InitPosition(redraw)
4617      int redraw;
4618 {
4619     ChessSquare (* pieces)[BOARD_SIZE];
4620     int i, j, pawnRow, overrule,
4621     oldx = gameInfo.boardWidth,
4622     oldy = gameInfo.boardHeight,
4623     oldh = gameInfo.holdingsWidth,
4624     oldv = gameInfo.variant;
4625
4626     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4627
4628     /* [AS] Initialize pv info list [HGM] and game status */
4629     {
4630         for( i=0; i<MAX_MOVES; i++ ) {
4631             pvInfoList[i].depth = 0;
4632             epStatus[i]=EP_NONE;
4633             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4634         }
4635
4636         initialRulePlies = 0; /* 50-move counter start */
4637
4638         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4639         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4640     }
4641
4642     
4643     /* [HGM] logic here is completely changed. In stead of full positions */
4644     /* the initialized data only consist of the two backranks. The switch */
4645     /* selects which one we will use, which is than copied to the Board   */
4646     /* initialPosition, which for the rest is initialized by Pawns and    */
4647     /* empty squares. This initial position is then copied to boards[0],  */
4648     /* possibly after shuffling, so that it remains available.            */
4649
4650     gameInfo.holdingsWidth = 0; /* default board sizes */
4651     gameInfo.boardWidth    = 8;
4652     gameInfo.boardHeight   = 8;
4653     gameInfo.holdingsSize  = 0;
4654     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4655     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4656     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4657
4658     switch (gameInfo.variant) {
4659     case VariantFischeRandom:
4660       shuffleOpenings = TRUE;
4661     default:
4662       pieces = FIDEArray;
4663       break;
4664     case VariantShatranj:
4665       pieces = ShatranjArray;
4666       nrCastlingRights = 0;
4667       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4668       break;
4669     case VariantTwoKings:
4670       pieces = twoKingsArray;
4671       break;
4672     case VariantCapaRandom:
4673       shuffleOpenings = TRUE;
4674     case VariantCapablanca:
4675       pieces = CapablancaArray;
4676       gameInfo.boardWidth = 10;
4677       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4678       break;
4679     case VariantGothic:
4680       pieces = GothicArray;
4681       gameInfo.boardWidth = 10;
4682       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4683       break;
4684     case VariantJanus:
4685       pieces = JanusArray;
4686       gameInfo.boardWidth = 10;
4687       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4688       nrCastlingRights = 6;
4689         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4690         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4691         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4693         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4694         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695       break;
4696     case VariantFalcon:
4697       pieces = FalconArray;
4698       gameInfo.boardWidth = 10;
4699       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4700       break;
4701     case VariantXiangqi:
4702       pieces = XiangqiArray;
4703       gameInfo.boardWidth  = 9;
4704       gameInfo.boardHeight = 10;
4705       nrCastlingRights = 0;
4706       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4707       break;
4708     case VariantShogi:
4709       pieces = ShogiArray;
4710       gameInfo.boardWidth  = 9;
4711       gameInfo.boardHeight = 9;
4712       gameInfo.holdingsSize = 7;
4713       nrCastlingRights = 0;
4714       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4715       break;
4716     case VariantCourier:
4717       pieces = CourierArray;
4718       gameInfo.boardWidth  = 12;
4719       nrCastlingRights = 0;
4720       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4721       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4722       break;
4723     case VariantKnightmate:
4724       pieces = KnightmateArray;
4725       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4726       break;
4727     case VariantFairy:
4728       pieces = fairyArray;
4729       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4730       break;
4731     case VariantGreat:
4732       pieces = GreatArray;
4733       gameInfo.boardWidth = 10;
4734       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4735       gameInfo.holdingsSize = 8;
4736       break;
4737     case VariantSuper:
4738       pieces = FIDEArray;
4739       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4740       gameInfo.holdingsSize = 8;
4741       startedFromSetupPosition = TRUE;
4742       break;
4743     case VariantCrazyhouse:
4744     case VariantBughouse:
4745       pieces = FIDEArray;
4746       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4747       gameInfo.holdingsSize = 5;
4748       break;
4749     case VariantWildCastle:
4750       pieces = FIDEArray;
4751       /* !!?shuffle with kings guaranteed to be on d or e file */
4752       shuffleOpenings = 1;
4753       break;
4754     case VariantNoCastle:
4755       pieces = FIDEArray;
4756       nrCastlingRights = 0;
4757       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4758       /* !!?unconstrained back-rank shuffle */
4759       shuffleOpenings = 1;
4760       break;
4761     }
4762
4763     overrule = 0;
4764     if(appData.NrFiles >= 0) {
4765         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4766         gameInfo.boardWidth = appData.NrFiles;
4767     }
4768     if(appData.NrRanks >= 0) {
4769         gameInfo.boardHeight = appData.NrRanks;
4770     }
4771     if(appData.holdingsSize >= 0) {
4772         i = appData.holdingsSize;
4773         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4774         gameInfo.holdingsSize = i;
4775     }
4776     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4777     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4778         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4779
4780     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4781     if(pawnRow < 1) pawnRow = 1;
4782
4783     /* User pieceToChar list overrules defaults */
4784     if(appData.pieceToCharTable != NULL)
4785         SetCharTable(pieceToChar, appData.pieceToCharTable);
4786
4787     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4788
4789         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4790             s = (ChessSquare) 0; /* account holding counts in guard band */
4791         for( i=0; i<BOARD_HEIGHT; i++ )
4792             initialPosition[i][j] = s;
4793
4794         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4795         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4796         initialPosition[pawnRow][j] = WhitePawn;
4797         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4798         if(gameInfo.variant == VariantXiangqi) {
4799             if(j&1) {
4800                 initialPosition[pawnRow][j] = 
4801                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4802                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4803                    initialPosition[2][j] = WhiteCannon;
4804                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4805                 }
4806             }
4807         }
4808         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4809     }
4810     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4811
4812             j=BOARD_LEFT+1;
4813             initialPosition[1][j] = WhiteBishop;
4814             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4815             j=BOARD_RGHT-2;
4816             initialPosition[1][j] = WhiteRook;
4817             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4818     }
4819
4820     if( nrCastlingRights == -1) {
4821         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4822         /*       This sets default castling rights from none to normal corners   */
4823         /* Variants with other castling rights must set them themselves above    */
4824         nrCastlingRights = 6;
4825        
4826         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4827         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4828         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4829         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4830         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4831         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4832      }
4833
4834      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4835      if(gameInfo.variant == VariantGreat) { // promotion commoners
4836         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4837         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4838         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4839         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4840      }
4841   if (appData.debugMode) {
4842     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4843   }
4844     if(shuffleOpenings) {
4845         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4846         startedFromSetupPosition = TRUE;
4847     }
4848     if(startedFromPositionFile) {
4849       /* [HGM] loadPos: use PositionFile for every new game */
4850       CopyBoard(initialPosition, filePosition);
4851       for(i=0; i<nrCastlingRights; i++)
4852           castlingRights[0][i] = initialRights[i] = fileRights[i];
4853       startedFromSetupPosition = TRUE;
4854     }
4855
4856     CopyBoard(boards[0], initialPosition);
4857
4858     if(oldx != gameInfo.boardWidth ||
4859        oldy != gameInfo.boardHeight ||
4860        oldh != gameInfo.holdingsWidth
4861 #ifdef GOTHIC
4862        || oldv == VariantGothic ||        // For licensing popups
4863        gameInfo.variant == VariantGothic
4864 #endif
4865 #ifdef FALCON
4866        || oldv == VariantFalcon ||
4867        gameInfo.variant == VariantFalcon
4868 #endif
4869                                          )
4870             InitDrawingSizes(-2 ,0);
4871
4872     if (redraw)
4873       DrawPosition(TRUE, boards[currentMove]);
4874 }
4875
4876 void
4877 SendBoard(cps, moveNum)
4878      ChessProgramState *cps;
4879      int moveNum;
4880 {
4881     char message[MSG_SIZ];
4882     
4883     if (cps->useSetboard) {
4884       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4885       sprintf(message, "setboard %s\n", fen);
4886       SendToProgram(message, cps);
4887       free(fen);
4888
4889     } else {
4890       ChessSquare *bp;
4891       int i, j;
4892       /* Kludge to set black to move, avoiding the troublesome and now
4893        * deprecated "black" command.
4894        */
4895       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4896
4897       SendToProgram("edit\n", cps);
4898       SendToProgram("#\n", cps);
4899       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4900         bp = &boards[moveNum][i][BOARD_LEFT];
4901         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4902           if ((int) *bp < (int) BlackPawn) {
4903             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4904                     AAA + j, ONE + i);
4905             if(message[0] == '+' || message[0] == '~') {
4906                 sprintf(message, "%c%c%c+\n",
4907                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4908                         AAA + j, ONE + i);
4909             }
4910             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4911                 message[1] = BOARD_RGHT   - 1 - j + '1';
4912                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4913             }
4914             SendToProgram(message, cps);
4915           }
4916         }
4917       }
4918     
4919       SendToProgram("c\n", cps);
4920       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4921         bp = &boards[moveNum][i][BOARD_LEFT];
4922         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4923           if (((int) *bp != (int) EmptySquare)
4924               && ((int) *bp >= (int) BlackPawn)) {
4925             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4926                     AAA + j, ONE + i);
4927             if(message[0] == '+' || message[0] == '~') {
4928                 sprintf(message, "%c%c%c+\n",
4929                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4930                         AAA + j, ONE + i);
4931             }
4932             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4933                 message[1] = BOARD_RGHT   - 1 - j + '1';
4934                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4935             }
4936             SendToProgram(message, cps);
4937           }
4938         }
4939       }
4940     
4941       SendToProgram(".\n", cps);
4942     }
4943     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4944 }
4945
4946 int
4947 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4948 {
4949     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4950     /* [HGM] add Shogi promotions */
4951     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4952     ChessSquare piece;
4953     ChessMove moveType;
4954     Boolean premove;
4955
4956     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4957     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4958
4959     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4960       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4961         return FALSE;
4962
4963     piece = boards[currentMove][fromY][fromX];
4964     if(gameInfo.variant == VariantShogi) {
4965         promotionZoneSize = 3;
4966         highestPromotingPiece = (int)WhiteFerz;
4967     }
4968
4969     // next weed out all moves that do not touch the promotion zone at all
4970     if((int)piece >= BlackPawn) {
4971         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4972              return FALSE;
4973         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4974     } else {
4975         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4976            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4977     }
4978
4979     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4980
4981     // weed out mandatory Shogi promotions
4982     if(gameInfo.variant == VariantShogi) {
4983         if(piece >= BlackPawn) {
4984             if(toY == 0 && piece == BlackPawn ||
4985                toY == 0 && piece == BlackQueen ||
4986                toY <= 1 && piece == BlackKnight) {
4987                 *promoChoice = '+';
4988                 return FALSE;
4989             }
4990         } else {
4991             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4992                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4993                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4994                 *promoChoice = '+';
4995                 return FALSE;
4996             }
4997         }
4998     }
4999
5000     // weed out obviously illegal Pawn moves
5001     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5002         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5003         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5004         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5005         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5006         // note we are not allowed to test for valid (non-)capture, due to premove
5007     }
5008
5009     // we either have a choice what to promote to, or (in Shogi) whether to promote
5010     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5011         *promoChoice = PieceToChar(BlackFerz);  // no choice
5012         return FALSE;
5013     }
5014     if(appData.alwaysPromoteToQueen) { // predetermined
5015         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5016              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5017         else *promoChoice = PieceToChar(BlackQueen);
5018         return FALSE;
5019     }
5020
5021     // suppress promotion popup on illegal moves that are not premoves
5022     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5023               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5024     if(appData.testLegality && !premove) {
5025         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5026                         epStatus[currentMove], castlingRights[currentMove],
5027                         fromY, fromX, toY, toX, NULLCHAR);
5028         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5029            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5030             return FALSE;
5031     }
5032
5033     return TRUE;
5034 }
5035
5036 int
5037 InPalace(row, column)
5038      int row, column;
5039 {   /* [HGM] for Xiangqi */
5040     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5041          column < (BOARD_WIDTH + 4)/2 &&
5042          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5043     return FALSE;
5044 }
5045
5046 int
5047 PieceForSquare (x, y)
5048      int x;
5049      int y;
5050 {
5051   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5052      return -1;
5053   else
5054      return boards[currentMove][y][x];
5055 }
5056
5057 int
5058 OKToStartUserMove(x, y)
5059      int x, y;
5060 {
5061     ChessSquare from_piece;
5062     int white_piece;
5063
5064     if (matchMode) return FALSE;
5065     if (gameMode == EditPosition) return TRUE;
5066
5067     if (x >= 0 && y >= 0)
5068       from_piece = boards[currentMove][y][x];
5069     else
5070       from_piece = EmptySquare;
5071
5072     if (from_piece == EmptySquare) return FALSE;
5073
5074     white_piece = (int)from_piece >= (int)WhitePawn &&
5075       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5076
5077     switch (gameMode) {
5078       case PlayFromGameFile:
5079       case AnalyzeFile:
5080       case TwoMachinesPlay:
5081       case EndOfGame:
5082         return FALSE;
5083
5084       case IcsObserving:
5085       case IcsIdle:
5086         return FALSE;
5087
5088       case MachinePlaysWhite:
5089       case IcsPlayingBlack:
5090         if (appData.zippyPlay) return FALSE;
5091         if (white_piece) {
5092             DisplayMoveError(_("You are playing Black"));
5093             return FALSE;
5094         }
5095         break;
5096
5097       case MachinePlaysBlack:
5098       case IcsPlayingWhite:
5099         if (appData.zippyPlay) return FALSE;
5100         if (!white_piece) {
5101             DisplayMoveError(_("You are playing White"));
5102             return FALSE;
5103         }
5104         break;
5105
5106       case EditGame:
5107         if (!white_piece && WhiteOnMove(currentMove)) {
5108             DisplayMoveError(_("It is White's turn"));
5109             return FALSE;
5110         }           
5111         if (white_piece && !WhiteOnMove(currentMove)) {
5112             DisplayMoveError(_("It is Black's turn"));
5113             return FALSE;
5114         }           
5115         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5116             /* Editing correspondence game history */
5117             /* Could disallow this or prompt for confirmation */
5118             cmailOldMove = -1;
5119         }
5120         if (currentMove < forwardMostMove) {
5121             /* Discarding moves */
5122             /* Could prompt for confirmation here,
5123                but I don't think that's such a good idea */
5124             forwardMostMove = currentMove;
5125         }
5126         break;
5127
5128       case BeginningOfGame:
5129         if (appData.icsActive) return FALSE;
5130         if (!appData.noChessProgram) {
5131             if (!white_piece) {
5132                 DisplayMoveError(_("You are playing White"));
5133                 return FALSE;
5134             }
5135         }
5136         break;
5137         
5138       case Training:
5139         if (!white_piece && WhiteOnMove(currentMove)) {
5140             DisplayMoveError(_("It is White's turn"));
5141             return FALSE;
5142         }           
5143         if (white_piece && !WhiteOnMove(currentMove)) {
5144             DisplayMoveError(_("It is Black's turn"));
5145             return FALSE;
5146         }           
5147         break;
5148
5149       default:
5150       case IcsExamining:
5151         break;
5152     }
5153     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5154         && gameMode != AnalyzeFile && gameMode != Training) {
5155         DisplayMoveError(_("Displayed position is not current"));
5156         return FALSE;
5157     }
5158     return TRUE;
5159 }
5160
5161 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5162 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5163 int lastLoadGameUseList = FALSE;
5164 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5165 ChessMove lastLoadGameStart = (ChessMove) 0;
5166
5167 ChessMove
5168 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5169      int fromX, fromY, toX, toY;
5170      int promoChar;
5171      Boolean captureOwn;
5172 {
5173     ChessMove moveType;
5174     ChessSquare pdown, pup;
5175
5176     /* Check if the user is playing in turn.  This is complicated because we
5177        let the user "pick up" a piece before it is his turn.  So the piece he
5178        tried to pick up may have been captured by the time he puts it down!
5179        Therefore we use the color the user is supposed to be playing in this
5180        test, not the color of the piece that is currently on the starting
5181        square---except in EditGame mode, where the user is playing both
5182        sides; fortunately there the capture race can't happen.  (It can
5183        now happen in IcsExamining mode, but that's just too bad.  The user
5184        will get a somewhat confusing message in that case.)
5185        */
5186
5187     switch (gameMode) {
5188       case PlayFromGameFile:
5189       case AnalyzeFile:
5190       case TwoMachinesPlay:
5191       case EndOfGame:
5192       case IcsObserving:
5193       case IcsIdle:
5194         /* We switched into a game mode where moves are not accepted,
5195            perhaps while the mouse button was down. */
5196         return ImpossibleMove;
5197
5198       case MachinePlaysWhite:
5199         /* User is moving for Black */
5200         if (WhiteOnMove(currentMove)) {
5201             DisplayMoveError(_("It is White's turn"));
5202             return ImpossibleMove;
5203         }
5204         break;
5205
5206       case MachinePlaysBlack:
5207         /* User is moving for White */
5208         if (!WhiteOnMove(currentMove)) {
5209             DisplayMoveError(_("It is Black's turn"));
5210             return ImpossibleMove;
5211         }
5212         break;
5213
5214       case EditGame:
5215       case IcsExamining:
5216       case BeginningOfGame:
5217       case AnalyzeMode:
5218       case Training:
5219         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5220             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5221             /* User is moving for Black */
5222             if (WhiteOnMove(currentMove)) {
5223                 DisplayMoveError(_("It is White's turn"));
5224                 return ImpossibleMove;
5225             }
5226         } else {
5227             /* User is moving for White */
5228             if (!WhiteOnMove(currentMove)) {
5229                 DisplayMoveError(_("It is Black's turn"));
5230                 return ImpossibleMove;
5231             }
5232         }
5233         break;
5234
5235       case IcsPlayingBlack:
5236         /* User is moving for Black */
5237         if (WhiteOnMove(currentMove)) {
5238             if (!appData.premove) {
5239                 DisplayMoveError(_("It is White's turn"));
5240             } else if (toX >= 0 && toY >= 0) {
5241                 premoveToX = toX;
5242                 premoveToY = toY;
5243                 premoveFromX = fromX;
5244                 premoveFromY = fromY;
5245                 premovePromoChar = promoChar;
5246                 gotPremove = 1;
5247                 if (appData.debugMode) 
5248                     fprintf(debugFP, "Got premove: fromX %d,"
5249                             "fromY %d, toX %d, toY %d\n",
5250                             fromX, fromY, toX, toY);
5251             }
5252             return ImpossibleMove;
5253         }
5254         break;
5255
5256       case IcsPlayingWhite:
5257         /* User is moving for White */
5258         if (!WhiteOnMove(currentMove)) {
5259             if (!appData.premove) {
5260                 DisplayMoveError(_("It is Black's turn"));
5261             } else if (toX >= 0 && toY >= 0) {
5262                 premoveToX = toX;
5263                 premoveToY = toY;
5264                 premoveFromX = fromX;
5265                 premoveFromY = fromY;
5266                 premovePromoChar = promoChar;
5267                 gotPremove = 1;
5268                 if (appData.debugMode) 
5269                     fprintf(debugFP, "Got premove: fromX %d,"
5270                             "fromY %d, toX %d, toY %d\n",
5271                             fromX, fromY, toX, toY);
5272             }
5273             return ImpossibleMove;
5274         }
5275         break;
5276
5277       default:
5278         break;
5279
5280       case EditPosition:
5281         /* EditPosition, empty square, or different color piece;
5282            click-click move is possible */
5283         if (toX == -2 || toY == -2) {
5284             boards[0][fromY][fromX] = EmptySquare;
5285             return AmbiguousMove;
5286         } else if (toX >= 0 && toY >= 0) {
5287             boards[0][toY][toX] = boards[0][fromY][fromX];
5288             boards[0][fromY][fromX] = EmptySquare;
5289             return AmbiguousMove;
5290         }
5291         return ImpossibleMove;
5292     }
5293
5294     if(toX < 0 || toY < 0) return ImpossibleMove;
5295     pdown = boards[currentMove][fromY][fromX];
5296     pup = boards[currentMove][toY][toX];
5297
5298     /* [HGM] If move started in holdings, it means a drop */
5299     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5300          if( pup != EmptySquare ) return ImpossibleMove;
5301          if(appData.testLegality) {
5302              /* it would be more logical if LegalityTest() also figured out
5303               * which drops are legal. For now we forbid pawns on back rank.
5304               * Shogi is on its own here...
5305               */
5306              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5307                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5308                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5309          }
5310          return WhiteDrop; /* Not needed to specify white or black yet */
5311     }
5312
5313     userOfferedDraw = FALSE;
5314         
5315     /* [HGM] always test for legality, to get promotion info */
5316     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5317                           epStatus[currentMove], castlingRights[currentMove],
5318                                          fromY, fromX, toY, toX, promoChar);
5319     /* [HGM] but possibly ignore an IllegalMove result */
5320     if (appData.testLegality) {
5321         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5322             DisplayMoveError(_("Illegal move"));
5323             return ImpossibleMove;
5324         }
5325     }
5326
5327     return moveType;
5328     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5329        function is made into one that returns an OK move type if FinishMove
5330        should be called. This to give the calling driver routine the
5331        opportunity to finish the userMove input with a promotion popup,
5332        without bothering the user with this for invalid or illegal moves */
5333
5334 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5335 }
5336
5337 /* Common tail of UserMoveEvent and DropMenuEvent */
5338 int
5339 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5340      ChessMove moveType;
5341      int fromX, fromY, toX, toY;
5342      /*char*/int promoChar;
5343 {
5344     char *bookHit = 0;
5345
5346     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5347         // [HGM] superchess: suppress promotions to non-available piece
5348         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5349         if(WhiteOnMove(currentMove)) {
5350             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5351         } else {
5352             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5353         }
5354     }
5355
5356     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5357        move type in caller when we know the move is a legal promotion */
5358     if(moveType == NormalMove && promoChar)
5359         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5360
5361     /* [HGM] convert drag-and-drop piece drops to standard form */
5362     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5363          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5364            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5365                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5366            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5367            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5368            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5369            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5370          fromY = DROP_RANK;
5371     }
5372
5373     /* [HGM] <popupFix> The following if has been moved here from
5374        UserMoveEvent(). Because it seemed to belong here (why not allow
5375        piece drops in training games?), and because it can only be
5376        performed after it is known to what we promote. */
5377     if (gameMode == Training) {
5378       /* compare the move played on the board to the next move in the
5379        * game. If they match, display the move and the opponent's response. 
5380        * If they don't match, display an error message.
5381        */
5382       int saveAnimate;
5383       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5384       CopyBoard(testBoard, boards[currentMove]);
5385       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5386
5387       if (CompareBoards(testBoard, boards[currentMove+1])) {
5388         ForwardInner(currentMove+1);
5389
5390         /* Autoplay the opponent's response.
5391          * if appData.animate was TRUE when Training mode was entered,
5392          * the response will be animated.
5393          */
5394         saveAnimate = appData.animate;
5395         appData.animate = animateTraining;
5396         ForwardInner(currentMove+1);
5397         appData.animate = saveAnimate;
5398
5399         /* check for the end of the game */
5400         if (currentMove >= forwardMostMove) {
5401           gameMode = PlayFromGameFile;
5402           ModeHighlight();
5403           SetTrainingModeOff();
5404           DisplayInformation(_("End of game"));
5405         }
5406       } else {
5407         DisplayError(_("Incorrect move"), 0);
5408       }
5409       return 1;
5410     }
5411
5412   /* Ok, now we know that the move is good, so we can kill
5413      the previous line in Analysis Mode */
5414   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5415     forwardMostMove = currentMove;
5416   }
5417
5418   /* If we need the chess program but it's dead, restart it */
5419   ResurrectChessProgram();
5420
5421   /* A user move restarts a paused game*/
5422   if (pausing)
5423     PauseEvent();
5424
5425   thinkOutput[0] = NULLCHAR;
5426
5427   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5428
5429   if (gameMode == BeginningOfGame) {
5430     if (appData.noChessProgram) {
5431       gameMode = EditGame;
5432       SetGameInfo();
5433     } else {
5434       char buf[MSG_SIZ];
5435       gameMode = MachinePlaysBlack;
5436       StartClocks();
5437       SetGameInfo();
5438       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5439       DisplayTitle(buf);
5440       if (first.sendName) {
5441         sprintf(buf, "name %s\n", gameInfo.white);
5442         SendToProgram(buf, &first);
5443       }
5444       StartClocks();
5445     }
5446     ModeHighlight();
5447   }
5448
5449   /* Relay move to ICS or chess engine */
5450   if (appData.icsActive) {
5451     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5452         gameMode == IcsExamining) {
5453       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5454       ics_user_moved = 1;
5455     }
5456   } else {
5457     if (first.sendTime && (gameMode == BeginningOfGame ||
5458                            gameMode == MachinePlaysWhite ||
5459                            gameMode == MachinePlaysBlack)) {
5460       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5461     }
5462     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5463          // [HGM] book: if program might be playing, let it use book
5464         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5465         first.maybeThinking = TRUE;
5466     } else SendMoveToProgram(forwardMostMove-1, &first);
5467     if (currentMove == cmailOldMove + 1) {
5468       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5469     }
5470   }
5471
5472   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5473
5474   switch (gameMode) {
5475   case EditGame:
5476     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5477                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5478     case MT_NONE:
5479     case MT_CHECK:
5480       break;
5481     case MT_CHECKMATE:
5482     case MT_STAINMATE:
5483       if (WhiteOnMove(currentMove)) {
5484         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5485       } else {
5486         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5487       }
5488       break;
5489     case MT_STALEMATE:
5490       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5491       break;
5492     }
5493     break;
5494     
5495   case MachinePlaysBlack:
5496   case MachinePlaysWhite:
5497     /* disable certain menu options while machine is thinking */
5498     SetMachineThinkingEnables();
5499     break;
5500
5501   default:
5502     break;
5503   }
5504
5505   if(bookHit) { // [HGM] book: simulate book reply
5506         static char bookMove[MSG_SIZ]; // a bit generous?
5507
5508         programStats.nodes = programStats.depth = programStats.time = 
5509         programStats.score = programStats.got_only_move = 0;
5510         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5511
5512         strcpy(bookMove, "move ");
5513         strcat(bookMove, bookHit);
5514         HandleMachineMove(bookMove, &first);
5515   }
5516   return 1;
5517 }
5518
5519 void
5520 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5521      int fromX, fromY, toX, toY;
5522      int promoChar;
5523 {
5524     /* [HGM] This routine was added to allow calling of its two logical
5525        parts from other modules in the old way. Before, UserMoveEvent()
5526        automatically called FinishMove() if the move was OK, and returned
5527        otherwise. I separated the two, in order to make it possible to
5528        slip a promotion popup in between. But that it always needs two
5529        calls, to the first part, (now called UserMoveTest() ), and to
5530        FinishMove if the first part succeeded. Calls that do not need
5531        to do anything in between, can call this routine the old way. 
5532     */
5533     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5534 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5535     if(moveType == AmbiguousMove)
5536         DrawPosition(FALSE, boards[currentMove]);
5537     else if(moveType != ImpossibleMove && moveType != Comment)
5538         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5539 }
5540
5541 void LeftClick(ClickType clickType, int xPix, int yPix)
5542 {
5543     int x, y;
5544     Boolean saveAnimate;
5545     static int second = 0, promotionChoice = 0;
5546     char promoChoice = NULLCHAR;
5547
5548     if (clickType == Press) ErrorPopDown();
5549
5550     x = EventToSquare(xPix, BOARD_WIDTH);
5551     y = EventToSquare(yPix, BOARD_HEIGHT);
5552     if (!flipView && y >= 0) {
5553         y = BOARD_HEIGHT - 1 - y;
5554     }
5555     if (flipView && x >= 0) {
5556         x = BOARD_WIDTH - 1 - x;
5557     }
5558
5559     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5560         if(clickType == Release) return; // ignore upclick of click-click destination
5561         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5562         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5563         if(gameInfo.holdingsWidth && 
5564                 (WhiteOnMove(currentMove) 
5565                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5566                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5567             // click in right holdings, for determining promotion piece
5568             ChessSquare p = boards[currentMove][y][x];
5569             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5570             if(p != EmptySquare) {
5571                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5572                 fromX = fromY = -1;
5573                 return;
5574             }
5575         }
5576         DrawPosition(FALSE, boards[currentMove]);
5577         return;
5578     }
5579
5580     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5581     if(clickType == Press
5582             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5583               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5584               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5585         return;
5586
5587     if (fromX == -1) {
5588         if (clickType == Press) {
5589             /* First square */
5590             if (OKToStartUserMove(x, y)) {
5591                 fromX = x;
5592                 fromY = y;
5593                 second = 0;
5594                 DragPieceBegin(xPix, yPix);
5595                 if (appData.highlightDragging) {
5596                     SetHighlights(x, y, -1, -1);
5597                 }
5598             }
5599         }
5600         return;
5601     }
5602
5603     /* fromX != -1 */
5604     if (clickType == Press && gameMode != EditPosition) {
5605         ChessSquare fromP;
5606         ChessSquare toP;
5607         int frc;
5608
5609         // ignore off-board to clicks
5610         if(y < 0 || x < 0) return;
5611
5612         /* Check if clicking again on the same color piece */
5613         fromP = boards[currentMove][fromY][fromX];
5614         toP = boards[currentMove][y][x];
5615         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5616         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5617              WhitePawn <= toP && toP <= WhiteKing &&
5618              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5619              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5620             (BlackPawn <= fromP && fromP <= BlackKing && 
5621              BlackPawn <= toP && toP <= BlackKing &&
5622              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5623              !(fromP == BlackKing && toP == BlackRook && frc))) {
5624             /* Clicked again on same color piece -- changed his mind */
5625             second = (x == fromX && y == fromY);
5626             if (appData.highlightDragging) {
5627                 SetHighlights(x, y, -1, -1);
5628             } else {
5629                 ClearHighlights();
5630             }
5631             if (OKToStartUserMove(x, y)) {
5632                 fromX = x;
5633                 fromY = y;
5634                 DragPieceBegin(xPix, yPix);
5635             }
5636             return;
5637         }
5638         // ignore clicks on holdings
5639         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5640     }
5641
5642     if (clickType == Release && x == fromX && y == fromY) {
5643         DragPieceEnd(xPix, yPix);
5644         if (appData.animateDragging) {
5645             /* Undo animation damage if any */
5646             DrawPosition(FALSE, NULL);
5647         }
5648         if (second) {
5649             /* Second up/down in same square; just abort move */
5650             second = 0;
5651             fromX = fromY = -1;
5652             ClearHighlights();
5653             gotPremove = 0;
5654             ClearPremoveHighlights();
5655         } else {
5656             /* First upclick in same square; start click-click mode */
5657             SetHighlights(x, y, -1, -1);
5658         }
5659         return;
5660     }
5661
5662     /* we now have a different from- and (possibly off-board) to-square */
5663     /* Completed move */
5664     toX = x;
5665     toY = y;
5666     saveAnimate = appData.animate;
5667     if (clickType == Press) {
5668         /* Finish clickclick move */
5669         if (appData.animate || appData.highlightLastMove) {
5670             SetHighlights(fromX, fromY, toX, toY);
5671         } else {
5672             ClearHighlights();
5673         }
5674     } else {
5675         /* Finish drag move */
5676         if (appData.highlightLastMove) {
5677             SetHighlights(fromX, fromY, toX, toY);
5678         } else {
5679             ClearHighlights();
5680         }
5681         DragPieceEnd(xPix, yPix);
5682         /* Don't animate move and drag both */
5683         appData.animate = FALSE;
5684     }
5685
5686     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5687     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5688         ClearHighlights();
5689         fromX = fromY = -1;
5690         DrawPosition(TRUE, NULL);
5691         return;
5692     }
5693
5694     // off-board moves should not be highlighted
5695     if(x < 0 || x < 0) ClearHighlights();
5696
5697     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5698         SetHighlights(fromX, fromY, toX, toY);
5699         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5700             // [HGM] super: promotion to captured piece selected from holdings
5701             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5702             promotionChoice = TRUE;
5703             // kludge follows to temporarily execute move on display, without promoting yet
5704             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5705             boards[currentMove][toY][toX] = p;
5706             DrawPosition(FALSE, boards[currentMove]);
5707             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5708             boards[currentMove][toY][toX] = q;
5709             DisplayMessage("Click in holdings to choose piece", "");
5710             return;
5711         }
5712         PromotionPopUp();
5713     } else {
5714         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5715         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5716         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5717         fromX = fromY = -1;
5718     }
5719     appData.animate = saveAnimate;
5720     if (appData.animate || appData.animateDragging) {
5721         /* Undo animation damage if needed */
5722         DrawPosition(FALSE, NULL);
5723     }
5724 }
5725
5726 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5727 {
5728 //    char * hint = lastHint;
5729     FrontEndProgramStats stats;
5730
5731     stats.which = cps == &first ? 0 : 1;
5732     stats.depth = cpstats->depth;
5733     stats.nodes = cpstats->nodes;
5734     stats.score = cpstats->score;
5735     stats.time = cpstats->time;
5736     stats.pv = cpstats->movelist;
5737     stats.hint = lastHint;
5738     stats.an_move_index = 0;
5739     stats.an_move_count = 0;
5740
5741     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5742         stats.hint = cpstats->move_name;
5743         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5744         stats.an_move_count = cpstats->nr_moves;
5745     }
5746
5747     SetProgramStats( &stats );
5748 }
5749
5750 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5751 {   // [HGM] book: this routine intercepts moves to simulate book replies
5752     char *bookHit = NULL;
5753
5754     //first determine if the incoming move brings opponent into his book
5755     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5756         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5757     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5758     if(bookHit != NULL && !cps->bookSuspend) {
5759         // make sure opponent is not going to reply after receiving move to book position
5760         SendToProgram("force\n", cps);
5761         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5762     }
5763     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5764     // now arrange restart after book miss
5765     if(bookHit) {
5766         // after a book hit we never send 'go', and the code after the call to this routine
5767         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5768         char buf[MSG_SIZ];
5769         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5770         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5771         SendToProgram(buf, cps);
5772         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5773     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5774         SendToProgram("go\n", cps);
5775         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5776     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5777         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5778             SendToProgram("go\n", cps); 
5779         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5780     }
5781     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5782 }
5783
5784 char *savedMessage;
5785 ChessProgramState *savedState;
5786 void DeferredBookMove(void)
5787 {
5788         if(savedState->lastPing != savedState->lastPong)
5789                     ScheduleDelayedEvent(DeferredBookMove, 10);
5790         else
5791         HandleMachineMove(savedMessage, savedState);
5792 }
5793
5794 void
5795 HandleMachineMove(message, cps)
5796      char *message;
5797      ChessProgramState *cps;
5798 {
5799     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5800     char realname[MSG_SIZ];
5801     int fromX, fromY, toX, toY;
5802     ChessMove moveType;
5803     char promoChar;
5804     char *p;
5805     int machineWhite;
5806     char *bookHit;
5807
5808 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5809     /*
5810      * Kludge to ignore BEL characters
5811      */
5812     while (*message == '\007') message++;
5813
5814     /*
5815      * [HGM] engine debug message: ignore lines starting with '#' character
5816      */
5817     if(cps->debug && *message == '#') return;
5818
5819     /*
5820      * Look for book output
5821      */
5822     if (cps == &first && bookRequested) {
5823         if (message[0] == '\t' || message[0] == ' ') {
5824             /* Part of the book output is here; append it */
5825             strcat(bookOutput, message);
5826             strcat(bookOutput, "  \n");
5827             return;
5828         } else if (bookOutput[0] != NULLCHAR) {
5829             /* All of book output has arrived; display it */
5830             char *p = bookOutput;
5831             while (*p != NULLCHAR) {
5832                 if (*p == '\t') *p = ' ';
5833                 p++;
5834             }
5835             DisplayInformation(bookOutput);
5836             bookRequested = FALSE;
5837             /* Fall through to parse the current output */
5838         }
5839     }
5840
5841     /*
5842      * Look for machine move.
5843      */
5844     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5845         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5846     {
5847         /* This method is only useful on engines that support ping */
5848         if (cps->lastPing != cps->lastPong) {
5849           if (gameMode == BeginningOfGame) {
5850             /* Extra move from before last new; ignore */
5851             if (appData.debugMode) {
5852                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5853             }
5854           } else {
5855             if (appData.debugMode) {
5856                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5857                         cps->which, gameMode);
5858             }
5859
5860             SendToProgram("undo\n", cps);
5861           }
5862           return;
5863         }
5864
5865         switch (gameMode) {
5866           case BeginningOfGame:
5867             /* Extra move from before last reset; ignore */
5868             if (appData.debugMode) {
5869                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5870             }
5871             return;
5872
5873           case EndOfGame:
5874           case IcsIdle:
5875           default:
5876             /* Extra move after we tried to stop.  The mode test is
5877                not a reliable way of detecting this problem, but it's
5878                the best we can do on engines that don't support ping.
5879             */
5880             if (appData.debugMode) {
5881                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5882                         cps->which, gameMode);
5883             }
5884             SendToProgram("undo\n", cps);
5885             return;
5886
5887           case MachinePlaysWhite:
5888           case IcsPlayingWhite:
5889             machineWhite = TRUE;
5890             break;
5891
5892           case MachinePlaysBlack:
5893           case IcsPlayingBlack:
5894             machineWhite = FALSE;
5895             break;
5896
5897           case TwoMachinesPlay:
5898             machineWhite = (cps->twoMachinesColor[0] == 'w');
5899             break;
5900         }
5901         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5902             if (appData.debugMode) {
5903                 fprintf(debugFP,
5904                         "Ignoring move out of turn by %s, gameMode %d"
5905                         ", forwardMost %d\n",
5906                         cps->which, gameMode, forwardMostMove);
5907             }
5908             return;
5909         }
5910
5911     if (appData.debugMode) { int f = forwardMostMove;
5912         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5913                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5914     }
5915         if(cps->alphaRank) AlphaRank(machineMove, 4);
5916         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5917                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5918             /* Machine move could not be parsed; ignore it. */
5919             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5920                     machineMove, cps->which);
5921             DisplayError(buf1, 0);
5922             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5923                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5924             if (gameMode == TwoMachinesPlay) {
5925               GameEnds(machineWhite ? BlackWins : WhiteWins,
5926                        buf1, GE_XBOARD);
5927             }
5928             return;
5929         }
5930
5931         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5932         /* So we have to redo legality test with true e.p. status here,  */
5933         /* to make sure an illegal e.p. capture does not slip through,   */
5934         /* to cause a forfeit on a justified illegal-move complaint      */
5935         /* of the opponent.                                              */
5936         if( gameMode==TwoMachinesPlay && appData.testLegality
5937             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5938                                                               ) {
5939            ChessMove moveType;
5940            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5941                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5942                              fromY, fromX, toY, toX, promoChar);
5943             if (appData.debugMode) {
5944                 int i;
5945                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5946                     castlingRights[forwardMostMove][i], castlingRank[i]);
5947                 fprintf(debugFP, "castling rights\n");
5948             }
5949             if(moveType == IllegalMove) {
5950                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5951                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5952                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5953                            buf1, GE_XBOARD);
5954                 return;
5955            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5956            /* [HGM] Kludge to handle engines that send FRC-style castling
5957               when they shouldn't (like TSCP-Gothic) */
5958            switch(moveType) {
5959              case WhiteASideCastleFR:
5960              case BlackASideCastleFR:
5961                toX+=2;
5962                currentMoveString[2]++;
5963                break;
5964              case WhiteHSideCastleFR:
5965              case BlackHSideCastleFR:
5966                toX--;
5967                currentMoveString[2]--;
5968                break;
5969              default: ; // nothing to do, but suppresses warning of pedantic compilers
5970            }
5971         }
5972         hintRequested = FALSE;
5973         lastHint[0] = NULLCHAR;
5974         bookRequested = FALSE;
5975         /* Program may be pondering now */
5976         cps->maybeThinking = TRUE;
5977         if (cps->sendTime == 2) cps->sendTime = 1;
5978         if (cps->offeredDraw) cps->offeredDraw--;
5979
5980 #if ZIPPY
5981         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5982             first.initDone) {
5983           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5984           ics_user_moved = 1;
5985           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5986                 char buf[3*MSG_SIZ];
5987
5988                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5989                         programStats.score / 100.,
5990                         programStats.depth,
5991                         programStats.time / 100.,
5992                         (unsigned int)programStats.nodes,
5993                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5994                         programStats.movelist);
5995                 SendToICS(buf);
5996 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5997           }
5998         }
5999 #endif
6000         /* currentMoveString is set as a side-effect of ParseOneMove */
6001         strcpy(machineMove, currentMoveString);
6002         strcat(machineMove, "\n");
6003         strcpy(moveList[forwardMostMove], machineMove);
6004
6005         /* [AS] Save move info and clear stats for next move */
6006         pvInfoList[ forwardMostMove ].score = programStats.score;
6007         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6008         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6009         ClearProgramStats();
6010         thinkOutput[0] = NULLCHAR;
6011         hiddenThinkOutputState = 0;
6012
6013         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6014
6015         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6016         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6017             int count = 0;
6018
6019             while( count < adjudicateLossPlies ) {
6020                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6021
6022                 if( count & 1 ) {
6023                     score = -score; /* Flip score for winning side */
6024                 }
6025
6026                 if( score > adjudicateLossThreshold ) {
6027                     break;
6028                 }
6029
6030                 count++;
6031             }
6032
6033             if( count >= adjudicateLossPlies ) {
6034                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6035
6036                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6037                     "Xboard adjudication", 
6038                     GE_XBOARD );
6039
6040                 return;
6041             }
6042         }
6043
6044         if( gameMode == TwoMachinesPlay ) {
6045           // [HGM] some adjudications useful with buggy engines
6046             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6047           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6048
6049
6050             if( appData.testLegality )
6051             {   /* [HGM] Some more adjudications for obstinate engines */
6052                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6053                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6054                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6055                 static int moveCount = 6;
6056                 ChessMove result;
6057                 char *reason = NULL;
6058
6059                 /* Count what is on board. */
6060                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6061                 {   ChessSquare p = boards[forwardMostMove][i][j];
6062                     int m=i;
6063
6064                     switch((int) p)
6065                     {   /* count B,N,R and other of each side */
6066                         case WhiteKing:
6067                         case BlackKing:
6068                              NrK++; break; // [HGM] atomic: count Kings
6069                         case WhiteKnight:
6070                              NrWN++; break;
6071                         case WhiteBishop:
6072                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6073                              bishopsColor |= 1 << ((i^j)&1);
6074                              NrWB++; break;
6075                         case BlackKnight:
6076                              NrBN++; break;
6077                         case BlackBishop:
6078                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6079                              bishopsColor |= 1 << ((i^j)&1);
6080                              NrBB++; break;
6081                         case WhiteRook:
6082                              NrWR++; break;
6083                         case BlackRook:
6084                              NrBR++; break;
6085                         case WhiteQueen:
6086                              NrWQ++; break;
6087                         case BlackQueen:
6088                              NrBQ++; break;
6089                         case EmptySquare: 
6090                              break;
6091                         case BlackPawn:
6092                              m = 7-i;
6093                         case WhitePawn:
6094                              PawnAdvance += m; NrPawns++;
6095                     }
6096                     NrPieces += (p != EmptySquare);
6097                     NrW += ((int)p < (int)BlackPawn);
6098                     if(gameInfo.variant == VariantXiangqi && 
6099                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6100                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6101                         NrW -= ((int)p < (int)BlackPawn);
6102                     }
6103                 }
6104
6105                 /* Some material-based adjudications that have to be made before stalemate test */
6106                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6107                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6108                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6109                      if(appData.checkMates) {
6110                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6111                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6113                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6114                          return;
6115                      }
6116                 }
6117
6118                 /* Bare King in Shatranj (loses) or Losers (wins) */
6119                 if( NrW == 1 || NrPieces - NrW == 1) {
6120                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6121                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6122                      if(appData.checkMates) {
6123                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6124                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6126                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6127                          return;
6128                      }
6129                   } else
6130                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6131                   {    /* bare King */
6132                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6133                         if(appData.checkMates) {
6134                             /* but only adjudicate if adjudication enabled */
6135                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6136                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6137                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6138                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6139                             return;
6140                         }
6141                   }
6142                 } else bare = 1;
6143
6144
6145             // don't wait for engine to announce game end if we can judge ourselves
6146             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6147                                        castlingRights[forwardMostMove]) ) {
6148               case MT_CHECK:
6149                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6150                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6151                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6152                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6153                             checkCnt++;
6154                         if(checkCnt >= 2) {
6155                             reason = "Xboard adjudication: 3rd check";
6156                             epStatus[forwardMostMove] = EP_CHECKMATE;
6157                             break;
6158                         }
6159                     }
6160                 }
6161               case MT_NONE:
6162               default:
6163                 break;
6164               case MT_STALEMATE:
6165               case MT_STAINMATE:
6166                 reason = "Xboard adjudication: Stalemate";
6167                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6168                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6169                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6170                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6171                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6172                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6173                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6174                                                                         EP_CHECKMATE : EP_WINS);
6175                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6176                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6177                 }
6178                 break;
6179               case MT_CHECKMATE:
6180                 reason = "Xboard adjudication: Checkmate";
6181                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6182                 break;
6183             }
6184
6185                 switch(i = epStatus[forwardMostMove]) {
6186                     case EP_STALEMATE:
6187                         result = GameIsDrawn; break;
6188                     case EP_CHECKMATE:
6189                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6190                     case EP_WINS:
6191                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6192                     default:
6193                         result = (ChessMove) 0;
6194                 }
6195                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6196                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6197                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6198                     GameEnds( result, reason, GE_XBOARD );
6199                     return;
6200                 }
6201
6202                 /* Next absolutely insufficient mating material. */
6203                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6204                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6205                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6206                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6207                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6208
6209                      /* always flag draws, for judging claims */
6210                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6211
6212                      if(appData.materialDraws) {
6213                          /* but only adjudicate them if adjudication enabled */
6214                          SendToProgram("force\n", cps->other); // suppress reply
6215                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6216                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6218                          return;
6219                      }
6220                 }
6221
6222                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6223                 if(NrPieces == 4 && 
6224                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6225                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6226                    || NrWN==2 || NrBN==2     /* KNNK */
6227                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6228                   ) ) {
6229                      if(--moveCount < 0 && appData.trivialDraws)
6230                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6231                           SendToProgram("force\n", cps->other); // suppress reply
6232                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6233                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6234                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6235                           return;
6236                      }
6237                 } else moveCount = 6;
6238             }
6239           }
6240
6241                 /* Check for rep-draws */
6242                 count = 0;
6243                 for(k = forwardMostMove-2;
6244                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6245                         epStatus[k] < EP_UNKNOWN &&
6246                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6247                     k-=2)
6248                 {   int rights=0;
6249                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6250                         /* compare castling rights */
6251                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6252                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6253                                 rights++; /* King lost rights, while rook still had them */
6254                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6255                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6256                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6257                                    rights++; /* but at least one rook lost them */
6258                         }
6259                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6260                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6261                                 rights++; 
6262                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6263                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6264                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6265                                    rights++;
6266                         }
6267                         if( rights == 0 && ++count > appData.drawRepeats-2
6268                             && appData.drawRepeats > 1) {
6269                              /* adjudicate after user-specified nr of repeats */
6270                              SendToProgram("force\n", cps->other); // suppress reply
6271                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6272                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6273                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6274                                 // [HGM] xiangqi: check for forbidden perpetuals
6275                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6276                                 for(m=forwardMostMove; m>k; m-=2) {
6277                                     if(MateTest(boards[m], PosFlags(m), 
6278                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6279                                         ourPerpetual = 0; // the current mover did not always check
6280                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6281                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6282                                         hisPerpetual = 0; // the opponent did not always check
6283                                 }
6284                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6285                                                                         ourPerpetual, hisPerpetual);
6286                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6287                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6288                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6289                                     return;
6290                                 }
6291                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6292                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6293                                 // Now check for perpetual chases
6294                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6295                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6296                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6297                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6298                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6299                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6300                                         return;
6301                                     }
6302                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6303                                         break; // Abort repetition-checking loop.
6304                                 }
6305                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6306                              }
6307                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6308                              return;
6309                         }
6310                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6311                              epStatus[forwardMostMove] = EP_REP_DRAW;
6312                     }
6313                 }
6314
6315                 /* Now we test for 50-move draws. Determine ply count */
6316                 count = forwardMostMove;
6317                 /* look for last irreversble move */
6318                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6319                     count--;
6320                 /* if we hit starting position, add initial plies */
6321                 if( count == backwardMostMove )
6322                     count -= initialRulePlies;
6323                 count = forwardMostMove - count; 
6324                 if( count >= 100)
6325                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6326                          /* this is used to judge if draw claims are legal */
6327                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6328                          SendToProgram("force\n", cps->other); // suppress reply
6329                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6330                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6331                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6332                          return;
6333                 }
6334
6335                 /* if draw offer is pending, treat it as a draw claim
6336                  * when draw condition present, to allow engines a way to
6337                  * claim draws before making their move to avoid a race
6338                  * condition occurring after their move
6339                  */
6340                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6341                          char *p = NULL;
6342                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6343                              p = "Draw claim: 50-move rule";
6344                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6345                              p = "Draw claim: 3-fold repetition";
6346                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6347                              p = "Draw claim: insufficient mating material";
6348                          if( p != NULL ) {
6349                              SendToProgram("force\n", cps->other); // suppress reply
6350                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6351                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6352                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6353                              return;
6354                          }
6355                 }
6356
6357
6358                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6359                     SendToProgram("force\n", cps->other); // suppress reply
6360                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6361                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6362
6363                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6364
6365                     return;
6366                 }
6367         }
6368
6369         bookHit = NULL;
6370         if (gameMode == TwoMachinesPlay) {
6371             /* [HGM] relaying draw offers moved to after reception of move */
6372             /* and interpreting offer as claim if it brings draw condition */
6373             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6374                 SendToProgram("draw\n", cps->other);
6375             }
6376             if (cps->other->sendTime) {
6377                 SendTimeRemaining(cps->other,
6378                                   cps->other->twoMachinesColor[0] == 'w');
6379             }
6380             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6381             if (firstMove && !bookHit) {
6382                 firstMove = FALSE;
6383                 if (cps->other->useColors) {
6384                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6385                 }
6386                 SendToProgram("go\n", cps->other);
6387             }
6388             cps->other->maybeThinking = TRUE;
6389         }
6390
6391         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6392         
6393         if (!pausing && appData.ringBellAfterMoves) {
6394             RingBell();
6395         }
6396
6397         /* 
6398          * Reenable menu items that were disabled while
6399          * machine was thinking
6400          */
6401         if (gameMode != TwoMachinesPlay)
6402             SetUserThinkingEnables();
6403
6404         // [HGM] book: after book hit opponent has received move and is now in force mode
6405         // force the book reply into it, and then fake that it outputted this move by jumping
6406         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6407         if(bookHit) {
6408                 static char bookMove[MSG_SIZ]; // a bit generous?
6409
6410                 strcpy(bookMove, "move ");
6411                 strcat(bookMove, bookHit);
6412                 message = bookMove;
6413                 cps = cps->other;
6414                 programStats.nodes = programStats.depth = programStats.time = 
6415                 programStats.score = programStats.got_only_move = 0;
6416                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6417
6418                 if(cps->lastPing != cps->lastPong) {
6419                     savedMessage = message; // args for deferred call
6420                     savedState = cps;
6421                     ScheduleDelayedEvent(DeferredBookMove, 10);
6422                     return;
6423                 }
6424                 goto FakeBookMove;
6425         }
6426
6427         return;
6428     }
6429
6430     /* Set special modes for chess engines.  Later something general
6431      *  could be added here; for now there is just one kludge feature,
6432      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6433      *  when "xboard" is given as an interactive command.
6434      */
6435     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6436         cps->useSigint = FALSE;
6437         cps->useSigterm = FALSE;
6438     }
6439     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6440       ParseFeatures(message+8, cps);
6441       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6442     }
6443
6444     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6445      * want this, I was asked to put it in, and obliged.
6446      */
6447     if (!strncmp(message, "setboard ", 9)) {
6448         Board initial_position; int i;
6449
6450         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6451
6452         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6453             DisplayError(_("Bad FEN received from engine"), 0);
6454             return ;
6455         } else {
6456            Reset(TRUE, FALSE);
6457            CopyBoard(boards[0], initial_position);
6458            initialRulePlies = FENrulePlies;
6459            epStatus[0] = FENepStatus;
6460            for( i=0; i<nrCastlingRights; i++ )
6461                 castlingRights[0][i] = FENcastlingRights[i];
6462            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6463            else gameMode = MachinePlaysBlack;                 
6464            DrawPosition(FALSE, boards[currentMove]);
6465         }
6466         return;
6467     }
6468
6469     /*
6470      * Look for communication commands
6471      */
6472     if (!strncmp(message, "telluser ", 9)) {
6473         DisplayNote(message + 9);
6474         return;
6475     }
6476     if (!strncmp(message, "tellusererror ", 14)) {
6477         DisplayError(message + 14, 0);
6478         return;
6479     }
6480     if (!strncmp(message, "tellopponent ", 13)) {
6481       if (appData.icsActive) {
6482         if (loggedOn) {
6483           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6484           SendToICS(buf1);
6485         }
6486       } else {
6487         DisplayNote(message + 13);
6488       }
6489       return;
6490     }
6491     if (!strncmp(message, "tellothers ", 11)) {
6492       if (appData.icsActive) {
6493         if (loggedOn) {
6494           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6495           SendToICS(buf1);
6496         }
6497       }
6498       return;
6499     }
6500     if (!strncmp(message, "tellall ", 8)) {
6501       if (appData.icsActive) {
6502         if (loggedOn) {
6503           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6504           SendToICS(buf1);
6505         }
6506       } else {
6507         DisplayNote(message + 8);
6508       }
6509       return;
6510     }
6511     if (strncmp(message, "warning", 7) == 0) {
6512         /* Undocumented feature, use tellusererror in new code */
6513         DisplayError(message, 0);
6514         return;
6515     }
6516     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6517         strcpy(realname, cps->tidy);
6518         strcat(realname, " query");
6519         AskQuestion(realname, buf2, buf1, cps->pr);
6520         return;
6521     }
6522     /* Commands from the engine directly to ICS.  We don't allow these to be 
6523      *  sent until we are logged on. Crafty kibitzes have been known to 
6524      *  interfere with the login process.
6525      */
6526     if (loggedOn) {
6527         if (!strncmp(message, "tellics ", 8)) {
6528             SendToICS(message + 8);
6529             SendToICS("\n");
6530             return;
6531         }
6532         if (!strncmp(message, "tellicsnoalias ", 15)) {
6533             SendToICS(ics_prefix);
6534             SendToICS(message + 15);
6535             SendToICS("\n");
6536             return;
6537         }
6538         /* The following are for backward compatibility only */
6539         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6540             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6541             SendToICS(ics_prefix);
6542             SendToICS(message);
6543             SendToICS("\n");
6544             return;
6545         }
6546     }
6547     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6548         return;
6549     }
6550     /*
6551      * If the move is illegal, cancel it and redraw the board.
6552      * Also deal with other error cases.  Matching is rather loose
6553      * here to accommodate engines written before the spec.
6554      */
6555     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6556         strncmp(message, "Error", 5) == 0) {
6557         if (StrStr(message, "name") || 
6558             StrStr(message, "rating") || StrStr(message, "?") ||
6559             StrStr(message, "result") || StrStr(message, "board") ||
6560             StrStr(message, "bk") || StrStr(message, "computer") ||
6561             StrStr(message, "variant") || StrStr(message, "hint") ||
6562             StrStr(message, "random") || StrStr(message, "depth") ||
6563             StrStr(message, "accepted")) {
6564             return;
6565         }
6566         if (StrStr(message, "protover")) {
6567           /* Program is responding to input, so it's apparently done
6568              initializing, and this error message indicates it is
6569              protocol version 1.  So we don't need to wait any longer
6570              for it to initialize and send feature commands. */
6571           FeatureDone(cps, 1);
6572           cps->protocolVersion = 1;
6573           return;
6574         }
6575         cps->maybeThinking = FALSE;
6576
6577         if (StrStr(message, "draw")) {
6578             /* Program doesn't have "draw" command */
6579             cps->sendDrawOffers = 0;
6580             return;
6581         }
6582         if (cps->sendTime != 1 &&
6583             (StrStr(message, "time") || StrStr(message, "otim"))) {
6584           /* Program apparently doesn't have "time" or "otim" command */
6585           cps->sendTime = 0;
6586           return;
6587         }
6588         if (StrStr(message, "analyze")) {
6589             cps->analysisSupport = FALSE;
6590             cps->analyzing = FALSE;
6591             Reset(FALSE, TRUE);
6592             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6593             DisplayError(buf2, 0);
6594             return;
6595         }
6596         if (StrStr(message, "(no matching move)st")) {
6597           /* Special kludge for GNU Chess 4 only */
6598           cps->stKludge = TRUE;
6599           SendTimeControl(cps, movesPerSession, timeControl,
6600                           timeIncrement, appData.searchDepth,
6601                           searchTime);
6602           return;
6603         }
6604         if (StrStr(message, "(no matching move)sd")) {
6605           /* Special kludge for GNU Chess 4 only */
6606           cps->sdKludge = TRUE;
6607           SendTimeControl(cps, movesPerSession, timeControl,
6608                           timeIncrement, appData.searchDepth,
6609                           searchTime);
6610           return;
6611         }
6612         if (!StrStr(message, "llegal")) {
6613             return;
6614         }
6615         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6616             gameMode == IcsIdle) return;
6617         if (forwardMostMove <= backwardMostMove) return;
6618         if (pausing) PauseEvent();
6619       if(appData.forceIllegal) {
6620             // [HGM] illegal: machine refused move; force position after move into it
6621           SendToProgram("force\n", cps);
6622           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6623                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6624                 // when black is to move, while there might be nothing on a2 or black
6625                 // might already have the move. So send the board as if white has the move.
6626                 // But first we must change the stm of the engine, as it refused the last move
6627                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6628                 if(WhiteOnMove(forwardMostMove)) {
6629                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6630                     SendBoard(cps, forwardMostMove); // kludgeless board
6631                 } else {
6632                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6633                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6634                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6635                 }
6636           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6637             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6638                  gameMode == TwoMachinesPlay)
6639               SendToProgram("go\n", cps);
6640             return;
6641       } else
6642         if (gameMode == PlayFromGameFile) {
6643             /* Stop reading this game file */
6644             gameMode = EditGame;
6645             ModeHighlight();
6646         }
6647         currentMove = --forwardMostMove;
6648         DisplayMove(currentMove-1); /* before DisplayMoveError */
6649         SwitchClocks();
6650         DisplayBothClocks();
6651         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6652                 parseList[currentMove], cps->which);
6653         DisplayMoveError(buf1);
6654         DrawPosition(FALSE, boards[currentMove]);
6655
6656         /* [HGM] illegal-move claim should forfeit game when Xboard */
6657         /* only passes fully legal moves                            */
6658         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6659             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6660                                 "False illegal-move claim", GE_XBOARD );
6661         }
6662         return;
6663     }
6664     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6665         /* Program has a broken "time" command that
6666            outputs a string not ending in newline.
6667            Don't use it. */
6668         cps->sendTime = 0;
6669     }
6670     
6671     /*
6672      * If chess program startup fails, exit with an error message.
6673      * Attempts to recover here are futile.
6674      */
6675     if ((StrStr(message, "unknown host") != NULL)
6676         || (StrStr(message, "No remote directory") != NULL)
6677         || (StrStr(message, "not found") != NULL)
6678         || (StrStr(message, "No such file") != NULL)
6679         || (StrStr(message, "can't alloc") != NULL)
6680         || (StrStr(message, "Permission denied") != NULL)) {
6681
6682         cps->maybeThinking = FALSE;
6683         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6684                 cps->which, cps->program, cps->host, message);
6685         RemoveInputSource(cps->isr);
6686         DisplayFatalError(buf1, 0, 1);
6687         return;
6688     }
6689     
6690     /* 
6691      * Look for hint output
6692      */
6693     if (sscanf(message, "Hint: %s", buf1) == 1) {
6694         if (cps == &first && hintRequested) {
6695             hintRequested = FALSE;
6696             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6697                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6698                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6699                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6700                                     fromY, fromX, toY, toX, promoChar, buf1);
6701                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6702                 DisplayInformation(buf2);
6703             } else {
6704                 /* Hint move could not be parsed!? */
6705               snprintf(buf2, sizeof(buf2),
6706                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6707                         buf1, cps->which);
6708                 DisplayError(buf2, 0);
6709             }
6710         } else {
6711             strcpy(lastHint, buf1);
6712         }
6713         return;
6714     }
6715
6716     /*
6717      * Ignore other messages if game is not in progress
6718      */
6719     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6720         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6721
6722     /*
6723      * look for win, lose, draw, or draw offer
6724      */
6725     if (strncmp(message, "1-0", 3) == 0) {
6726         char *p, *q, *r = "";
6727         p = strchr(message, '{');
6728         if (p) {
6729             q = strchr(p, '}');
6730             if (q) {
6731                 *q = NULLCHAR;
6732                 r = p + 1;
6733             }
6734         }
6735         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6736         return;
6737     } else if (strncmp(message, "0-1", 3) == 0) {
6738         char *p, *q, *r = "";
6739         p = strchr(message, '{');
6740         if (p) {
6741             q = strchr(p, '}');
6742             if (q) {
6743                 *q = NULLCHAR;
6744                 r = p + 1;
6745             }
6746         }
6747         /* Kludge for Arasan 4.1 bug */
6748         if (strcmp(r, "Black resigns") == 0) {
6749             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6750             return;
6751         }
6752         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6753         return;
6754     } else if (strncmp(message, "1/2", 3) == 0) {
6755         char *p, *q, *r = "";
6756         p = strchr(message, '{');
6757         if (p) {
6758             q = strchr(p, '}');
6759             if (q) {
6760                 *q = NULLCHAR;
6761                 r = p + 1;
6762             }
6763         }
6764             
6765         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6766         return;
6767
6768     } else if (strncmp(message, "White resign", 12) == 0) {
6769         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6770         return;
6771     } else if (strncmp(message, "Black resign", 12) == 0) {
6772         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6773         return;
6774     } else if (strncmp(message, "White matches", 13) == 0 ||
6775                strncmp(message, "Black matches", 13) == 0   ) {
6776         /* [HGM] ignore GNUShogi noises */
6777         return;
6778     } else if (strncmp(message, "White", 5) == 0 &&
6779                message[5] != '(' &&
6780                StrStr(message, "Black") == NULL) {
6781         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6782         return;
6783     } else if (strncmp(message, "Black", 5) == 0 &&
6784                message[5] != '(') {
6785         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6786         return;
6787     } else if (strcmp(message, "resign") == 0 ||
6788                strcmp(message, "computer resigns") == 0) {
6789         switch (gameMode) {
6790           case MachinePlaysBlack:
6791           case IcsPlayingBlack:
6792             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6793             break;
6794           case MachinePlaysWhite:
6795           case IcsPlayingWhite:
6796             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6797             break;
6798           case TwoMachinesPlay:
6799             if (cps->twoMachinesColor[0] == 'w')
6800               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6801             else
6802               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6803             break;
6804           default:
6805             /* can't happen */
6806             break;
6807         }
6808         return;
6809     } else if (strncmp(message, "opponent mates", 14) == 0) {
6810         switch (gameMode) {
6811           case MachinePlaysBlack:
6812           case IcsPlayingBlack:
6813             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6814             break;
6815           case MachinePlaysWhite:
6816           case IcsPlayingWhite:
6817             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6818             break;
6819           case TwoMachinesPlay:
6820             if (cps->twoMachinesColor[0] == 'w')
6821               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6822             else
6823               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6824             break;
6825           default:
6826             /* can't happen */
6827             break;
6828         }
6829         return;
6830     } else if (strncmp(message, "computer mates", 14) == 0) {
6831         switch (gameMode) {
6832           case MachinePlaysBlack:
6833           case IcsPlayingBlack:
6834             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6835             break;
6836           case MachinePlaysWhite:
6837           case IcsPlayingWhite:
6838             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6839             break;
6840           case TwoMachinesPlay:
6841             if (cps->twoMachinesColor[0] == 'w')
6842               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6843             else
6844               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6845             break;
6846           default:
6847             /* can't happen */
6848             break;
6849         }
6850         return;
6851     } else if (strncmp(message, "checkmate", 9) == 0) {
6852         if (WhiteOnMove(forwardMostMove)) {
6853             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6854         } else {
6855             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6856         }
6857         return;
6858     } else if (strstr(message, "Draw") != NULL ||
6859                strstr(message, "game is a draw") != NULL) {
6860         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6861         return;
6862     } else if (strstr(message, "offer") != NULL &&
6863                strstr(message, "draw") != NULL) {
6864 #if ZIPPY
6865         if (appData.zippyPlay && first.initDone) {
6866             /* Relay offer to ICS */
6867             SendToICS(ics_prefix);
6868             SendToICS("draw\n");
6869         }
6870 #endif
6871         cps->offeredDraw = 2; /* valid until this engine moves twice */
6872         if (gameMode == TwoMachinesPlay) {
6873             if (cps->other->offeredDraw) {
6874                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6875             /* [HGM] in two-machine mode we delay relaying draw offer      */
6876             /* until after we also have move, to see if it is really claim */
6877             }
6878         } else if (gameMode == MachinePlaysWhite ||
6879                    gameMode == MachinePlaysBlack) {
6880           if (userOfferedDraw) {
6881             DisplayInformation(_("Machine accepts your draw offer"));
6882             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6883           } else {
6884             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6885           }
6886         }
6887     }
6888
6889     
6890     /*
6891      * Look for thinking output
6892      */
6893     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6894           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6895                                 ) {
6896         int plylev, mvleft, mvtot, curscore, time;
6897         char mvname[MOVE_LEN];
6898         u64 nodes; // [DM]
6899         char plyext;
6900         int ignore = FALSE;
6901         int prefixHint = FALSE;
6902         mvname[0] = NULLCHAR;
6903
6904         switch (gameMode) {
6905           case MachinePlaysBlack:
6906           case IcsPlayingBlack:
6907             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6908             break;
6909           case MachinePlaysWhite:
6910           case IcsPlayingWhite:
6911             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6912             break;
6913           case AnalyzeMode:
6914           case AnalyzeFile:
6915             break;
6916           case IcsObserving: /* [DM] icsEngineAnalyze */
6917             if (!appData.icsEngineAnalyze) ignore = TRUE;
6918             break;
6919           case TwoMachinesPlay:
6920             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6921                 ignore = TRUE;
6922             }
6923             break;
6924           default:
6925             ignore = TRUE;
6926             break;
6927         }
6928
6929         if (!ignore) {
6930             buf1[0] = NULLCHAR;
6931             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6932                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6933
6934                 if (plyext != ' ' && plyext != '\t') {
6935                     time *= 100;
6936                 }
6937
6938                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6939                 if( cps->scoreIsAbsolute && 
6940                     ( gameMode == MachinePlaysBlack ||
6941                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
6942                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
6943                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
6944                      !WhiteOnMove(currentMove)
6945                     ) )
6946                 {
6947                     curscore = -curscore;
6948                 }
6949
6950
6951                 programStats.depth = plylev;
6952                 programStats.nodes = nodes;
6953                 programStats.time = time;
6954                 programStats.score = curscore;
6955                 programStats.got_only_move = 0;
6956
6957                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6958                         int ticklen;
6959
6960                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6961                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6962                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6963                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6964                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6965                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6966                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
6967                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6968                 }
6969
6970                 /* Buffer overflow protection */
6971                 if (buf1[0] != NULLCHAR) {
6972                     if (strlen(buf1) >= sizeof(programStats.movelist)
6973                         && appData.debugMode) {
6974                         fprintf(debugFP,
6975                                 "PV is too long; using the first %u bytes.\n",
6976                                 (unsigned) sizeof(programStats.movelist) - 1);
6977                     }
6978
6979                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6980                 } else {
6981                     sprintf(programStats.movelist, " no PV\n");
6982                 }
6983
6984                 if (programStats.seen_stat) {
6985                     programStats.ok_to_send = 1;
6986                 }
6987
6988                 if (strchr(programStats.movelist, '(') != NULL) {
6989                     programStats.line_is_book = 1;
6990                     programStats.nr_moves = 0;
6991                     programStats.moves_left = 0;
6992                 } else {
6993                     programStats.line_is_book = 0;
6994                 }
6995
6996                 SendProgramStatsToFrontend( cps, &programStats );
6997
6998                 /* 
6999                     [AS] Protect the thinkOutput buffer from overflow... this
7000                     is only useful if buf1 hasn't overflowed first!
7001                 */
7002                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7003                         plylev, 
7004                         (gameMode == TwoMachinesPlay ?
7005                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7006                         ((double) curscore) / 100.0,
7007                         prefixHint ? lastHint : "",
7008                         prefixHint ? " " : "" );
7009
7010                 if( buf1[0] != NULLCHAR ) {
7011                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7012
7013                     if( strlen(buf1) > max_len ) {
7014                         if( appData.debugMode) {
7015                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7016                         }
7017                         buf1[max_len+1] = '\0';
7018                     }
7019
7020                     strcat( thinkOutput, buf1 );
7021                 }
7022
7023                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7024                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7025                     DisplayMove(currentMove - 1);
7026                 }
7027                 return;
7028
7029             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7030                 /* crafty (9.25+) says "(only move) <move>"
7031                  * if there is only 1 legal move
7032                  */
7033                 sscanf(p, "(only move) %s", buf1);
7034                 sprintf(thinkOutput, "%s (only move)", buf1);
7035                 sprintf(programStats.movelist, "%s (only move)", buf1);
7036                 programStats.depth = 1;
7037                 programStats.nr_moves = 1;
7038                 programStats.moves_left = 1;
7039                 programStats.nodes = 1;
7040                 programStats.time = 1;
7041                 programStats.got_only_move = 1;
7042
7043                 /* Not really, but we also use this member to
7044                    mean "line isn't going to change" (Crafty
7045                    isn't searching, so stats won't change) */
7046                 programStats.line_is_book = 1;
7047
7048                 SendProgramStatsToFrontend( cps, &programStats );
7049                 
7050                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7051                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7052                     DisplayMove(currentMove - 1);
7053                 }
7054                 return;
7055             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7056                               &time, &nodes, &plylev, &mvleft,
7057                               &mvtot, mvname) >= 5) {
7058                 /* The stat01: line is from Crafty (9.29+) in response
7059                    to the "." command */
7060                 programStats.seen_stat = 1;
7061                 cps->maybeThinking = TRUE;
7062
7063                 if (programStats.got_only_move || !appData.periodicUpdates)
7064                   return;
7065
7066                 programStats.depth = plylev;
7067                 programStats.time = time;
7068                 programStats.nodes = nodes;
7069                 programStats.moves_left = mvleft;
7070                 programStats.nr_moves = mvtot;
7071                 strcpy(programStats.move_name, mvname);
7072                 programStats.ok_to_send = 1;
7073                 programStats.movelist[0] = '\0';
7074
7075                 SendProgramStatsToFrontend( cps, &programStats );
7076
7077                 return;
7078
7079             } else if (strncmp(message,"++",2) == 0) {
7080                 /* Crafty 9.29+ outputs this */
7081                 programStats.got_fail = 2;
7082                 return;
7083
7084             } else if (strncmp(message,"--",2) == 0) {
7085                 /* Crafty 9.29+ outputs this */
7086                 programStats.got_fail = 1;
7087                 return;
7088
7089             } else if (thinkOutput[0] != NULLCHAR &&
7090                        strncmp(message, "    ", 4) == 0) {
7091                 unsigned message_len;
7092
7093                 p = message;
7094                 while (*p && *p == ' ') p++;
7095
7096                 message_len = strlen( p );
7097
7098                 /* [AS] Avoid buffer overflow */
7099                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7100                     strcat(thinkOutput, " ");
7101                     strcat(thinkOutput, p);
7102                 }
7103
7104                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7105                     strcat(programStats.movelist, " ");
7106                     strcat(programStats.movelist, p);
7107                 }
7108
7109                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7110                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7111                     DisplayMove(currentMove - 1);
7112                 }
7113                 return;
7114             }
7115         }
7116         else {
7117             buf1[0] = NULLCHAR;
7118
7119             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7120                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7121             {
7122                 ChessProgramStats cpstats;
7123
7124                 if (plyext != ' ' && plyext != '\t') {
7125                     time *= 100;
7126                 }
7127
7128                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7129                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7130                     curscore = -curscore;
7131                 }
7132
7133                 cpstats.depth = plylev;
7134                 cpstats.nodes = nodes;
7135                 cpstats.time = time;
7136                 cpstats.score = curscore;
7137                 cpstats.got_only_move = 0;
7138                 cpstats.movelist[0] = '\0';
7139
7140                 if (buf1[0] != NULLCHAR) {
7141                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7142                 }
7143
7144                 cpstats.ok_to_send = 0;
7145                 cpstats.line_is_book = 0;
7146                 cpstats.nr_moves = 0;
7147                 cpstats.moves_left = 0;
7148
7149                 SendProgramStatsToFrontend( cps, &cpstats );
7150             }
7151         }
7152     }
7153 }
7154
7155
7156 /* Parse a game score from the character string "game", and
7157    record it as the history of the current game.  The game
7158    score is NOT assumed to start from the standard position. 
7159    The display is not updated in any way.
7160    */
7161 void
7162 ParseGameHistory(game)
7163      char *game;
7164 {
7165     ChessMove moveType;
7166     int fromX, fromY, toX, toY, boardIndex;
7167     char promoChar;
7168     char *p, *q;
7169     char buf[MSG_SIZ];
7170
7171     if (appData.debugMode)
7172       fprintf(debugFP, "Parsing game history: %s\n", game);
7173
7174     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7175     gameInfo.site = StrSave(appData.icsHost);
7176     gameInfo.date = PGNDate();
7177     gameInfo.round = StrSave("-");
7178
7179     /* Parse out names of players */
7180     while (*game == ' ') game++;
7181     p = buf;
7182     while (*game != ' ') *p++ = *game++;
7183     *p = NULLCHAR;
7184     gameInfo.white = StrSave(buf);
7185     while (*game == ' ') game++;
7186     p = buf;
7187     while (*game != ' ' && *game != '\n') *p++ = *game++;
7188     *p = NULLCHAR;
7189     gameInfo.black = StrSave(buf);
7190
7191     /* Parse moves */
7192     boardIndex = blackPlaysFirst ? 1 : 0;
7193     yynewstr(game);
7194     for (;;) {
7195         yyboardindex = boardIndex;
7196         moveType = (ChessMove) yylex();
7197         switch (moveType) {
7198           case IllegalMove:             /* maybe suicide chess, etc. */
7199   if (appData.debugMode) {
7200     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7201     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7202     setbuf(debugFP, NULL);
7203   }
7204           case WhitePromotionChancellor:
7205           case BlackPromotionChancellor:
7206           case WhitePromotionArchbishop:
7207           case BlackPromotionArchbishop:
7208           case WhitePromotionQueen:
7209           case BlackPromotionQueen:
7210           case WhitePromotionRook:
7211           case BlackPromotionRook:
7212           case WhitePromotionBishop:
7213           case BlackPromotionBishop:
7214           case WhitePromotionKnight:
7215           case BlackPromotionKnight:
7216           case WhitePromotionKing:
7217           case BlackPromotionKing:
7218           case NormalMove:
7219           case WhiteCapturesEnPassant:
7220           case BlackCapturesEnPassant:
7221           case WhiteKingSideCastle:
7222           case WhiteQueenSideCastle:
7223           case BlackKingSideCastle:
7224           case BlackQueenSideCastle:
7225           case WhiteKingSideCastleWild:
7226           case WhiteQueenSideCastleWild:
7227           case BlackKingSideCastleWild:
7228           case BlackQueenSideCastleWild:
7229           /* PUSH Fabien */
7230           case WhiteHSideCastleFR:
7231           case WhiteASideCastleFR:
7232           case BlackHSideCastleFR:
7233           case BlackASideCastleFR:
7234           /* POP Fabien */
7235             fromX = currentMoveString[0] - AAA;
7236             fromY = currentMoveString[1] - ONE;
7237             toX = currentMoveString[2] - AAA;
7238             toY = currentMoveString[3] - ONE;
7239             promoChar = currentMoveString[4];
7240             break;
7241           case WhiteDrop:
7242           case BlackDrop:
7243             fromX = moveType == WhiteDrop ?
7244               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7245             (int) CharToPiece(ToLower(currentMoveString[0]));
7246             fromY = DROP_RANK;
7247             toX = currentMoveString[2] - AAA;
7248             toY = currentMoveString[3] - ONE;
7249             promoChar = NULLCHAR;
7250             break;
7251           case AmbiguousMove:
7252             /* bug? */
7253             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7254   if (appData.debugMode) {
7255     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
7262             /* bug? */
7263             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7264   if (appData.debugMode) {
7265     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7266     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7267     setbuf(debugFP, NULL);
7268   }
7269             DisplayError(buf, 0);
7270             return;
7271           case (ChessMove) 0:   /* end of file */
7272             if (boardIndex < backwardMostMove) {
7273                 /* Oops, gap.  How did that happen? */
7274                 DisplayError(_("Gap in move list"), 0);
7275                 return;
7276             }
7277             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7278             if (boardIndex > forwardMostMove) {
7279                 forwardMostMove = boardIndex;
7280             }
7281             return;
7282           case ElapsedTime:
7283             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7284                 strcat(parseList[boardIndex-1], " ");
7285                 strcat(parseList[boardIndex-1], yy_text);
7286             }
7287             continue;
7288           case Comment:
7289           case PGNTag:
7290           case NAG:
7291           default:
7292             /* ignore */
7293             continue;
7294           case WhiteWins:
7295           case BlackWins:
7296           case GameIsDrawn:
7297           case GameUnfinished:
7298             if (gameMode == IcsExamining) {
7299                 if (boardIndex < backwardMostMove) {
7300                     /* Oops, gap.  How did that happen? */
7301                     return;
7302                 }
7303                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7304                 return;
7305             }
7306             gameInfo.result = moveType;
7307             p = strchr(yy_text, '{');
7308             if (p == NULL) p = strchr(yy_text, '(');
7309             if (p == NULL) {
7310                 p = yy_text;
7311                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7312             } else {
7313                 q = strchr(p, *p == '{' ? '}' : ')');
7314                 if (q != NULL) *q = NULLCHAR;
7315                 p++;
7316             }
7317             gameInfo.resultDetails = StrSave(p);
7318             continue;
7319         }
7320         if (boardIndex >= forwardMostMove &&
7321             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7322             backwardMostMove = blackPlaysFirst ? 1 : 0;
7323             return;
7324         }
7325         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7326                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7327                                  parseList[boardIndex]);
7328         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7329         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7330         /* currentMoveString is set as a side-effect of yylex */
7331         strcpy(moveList[boardIndex], currentMoveString);
7332         strcat(moveList[boardIndex], "\n");
7333         boardIndex++;
7334         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7335                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7336         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7337                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7338           case MT_NONE:
7339           case MT_STALEMATE:
7340           default:
7341             break;
7342           case MT_CHECK:
7343             if(gameInfo.variant != VariantShogi)
7344                 strcat(parseList[boardIndex - 1], "+");
7345             break;
7346           case MT_CHECKMATE:
7347           case MT_STAINMATE:
7348             strcat(parseList[boardIndex - 1], "#");
7349             break;
7350         }
7351     }
7352 }
7353
7354
7355 /* Apply a move to the given board  */
7356 void
7357 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7358      int fromX, fromY, toX, toY;
7359      int promoChar;
7360      Board board;
7361      char *castling;
7362      char *ep;
7363 {
7364   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7365
7366     /* [HGM] compute & store e.p. status and castling rights for new position */
7367     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7368     { int i;
7369
7370       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7371       oldEP = *ep;
7372       *ep = EP_NONE;
7373
7374       if( board[toY][toX] != EmptySquare ) 
7375            *ep = EP_CAPTURE;  
7376
7377       if( board[fromY][fromX] == WhitePawn ) {
7378            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7379                *ep = EP_PAWN_MOVE;
7380            if( toY-fromY==2) {
7381                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7382                         gameInfo.variant != VariantBerolina || toX < fromX)
7383                       *ep = toX | berolina;
7384                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7385                         gameInfo.variant != VariantBerolina || toX > fromX) 
7386                       *ep = toX;
7387            }
7388       } else 
7389       if( board[fromY][fromX] == BlackPawn ) {
7390            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7391                *ep = EP_PAWN_MOVE; 
7392            if( toY-fromY== -2) {
7393                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7394                         gameInfo.variant != VariantBerolina || toX < fromX)
7395                       *ep = toX | berolina;
7396                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7397                         gameInfo.variant != VariantBerolina || toX > fromX) 
7398                       *ep = toX;
7399            }
7400        }
7401
7402        for(i=0; i<nrCastlingRights; i++) {
7403            if(castling[i] == fromX && castlingRank[i] == fromY ||
7404               castling[i] == toX   && castlingRank[i] == toY   
7405              ) castling[i] = -1; // revoke for moved or captured piece
7406        }
7407
7408     }
7409
7410   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7411   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7412        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7413          
7414   if (fromX == toX && fromY == toY) return;
7415
7416   if (fromY == DROP_RANK) {
7417         /* must be first */
7418         piece = board[toY][toX] = (ChessSquare) fromX;
7419   } else {
7420      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7421      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7422      if(gameInfo.variant == VariantKnightmate)
7423          king += (int) WhiteUnicorn - (int) WhiteKing;
7424
7425     /* Code added by Tord: */
7426     /* FRC castling assumed when king captures friendly rook. */
7427     if (board[fromY][fromX] == WhiteKing &&
7428              board[toY][toX] == WhiteRook) {
7429       board[fromY][fromX] = EmptySquare;
7430       board[toY][toX] = EmptySquare;
7431       if(toX > fromX) {
7432         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7433       } else {
7434         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7435       }
7436     } else if (board[fromY][fromX] == BlackKing &&
7437                board[toY][toX] == BlackRook) {
7438       board[fromY][fromX] = EmptySquare;
7439       board[toY][toX] = EmptySquare;
7440       if(toX > fromX) {
7441         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7442       } else {
7443         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7444       }
7445     /* End of code added by Tord */
7446
7447     } else if (board[fromY][fromX] == king
7448         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7449         && toY == fromY && toX > fromX+1) {
7450         board[fromY][fromX] = EmptySquare;
7451         board[toY][toX] = king;
7452         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7453         board[fromY][BOARD_RGHT-1] = EmptySquare;
7454     } else if (board[fromY][fromX] == king
7455         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7456                && toY == fromY && toX < fromX-1) {
7457         board[fromY][fromX] = EmptySquare;
7458         board[toY][toX] = king;
7459         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7460         board[fromY][BOARD_LEFT] = EmptySquare;
7461     } else if (board[fromY][fromX] == WhitePawn
7462                && toY == BOARD_HEIGHT-1
7463                && gameInfo.variant != VariantXiangqi
7464                ) {
7465         /* white pawn promotion */
7466         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7467         if (board[toY][toX] == EmptySquare) {
7468             board[toY][toX] = WhiteQueen;
7469         }
7470         if(gameInfo.variant==VariantBughouse ||
7471            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7472             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7473         board[fromY][fromX] = EmptySquare;
7474     } else if ((fromY == BOARD_HEIGHT-4)
7475                && (toX != fromX)
7476                && gameInfo.variant != VariantXiangqi
7477                && gameInfo.variant != VariantBerolina
7478                && (board[fromY][fromX] == WhitePawn)
7479                && (board[toY][toX] == EmptySquare)) {
7480         board[fromY][fromX] = EmptySquare;
7481         board[toY][toX] = WhitePawn;
7482         captured = board[toY - 1][toX];
7483         board[toY - 1][toX] = EmptySquare;
7484     } else if ((fromY == BOARD_HEIGHT-4)
7485                && (toX == fromX)
7486                && gameInfo.variant == VariantBerolina
7487                && (board[fromY][fromX] == WhitePawn)
7488                && (board[toY][toX] == EmptySquare)) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = WhitePawn;
7491         if(oldEP & EP_BEROLIN_A) {
7492                 captured = board[fromY][fromX-1];
7493                 board[fromY][fromX-1] = EmptySquare;
7494         }else{  captured = board[fromY][fromX+1];
7495                 board[fromY][fromX+1] = EmptySquare;
7496         }
7497     } else if (board[fromY][fromX] == king
7498         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7499                && toY == fromY && toX > fromX+1) {
7500         board[fromY][fromX] = EmptySquare;
7501         board[toY][toX] = king;
7502         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7503         board[fromY][BOARD_RGHT-1] = EmptySquare;
7504     } else if (board[fromY][fromX] == king
7505         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7506                && toY == fromY && toX < fromX-1) {
7507         board[fromY][fromX] = EmptySquare;
7508         board[toY][toX] = king;
7509         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7510         board[fromY][BOARD_LEFT] = EmptySquare;
7511     } else if (fromY == 7 && fromX == 3
7512                && board[fromY][fromX] == BlackKing
7513                && toY == 7 && toX == 5) {
7514         board[fromY][fromX] = EmptySquare;
7515         board[toY][toX] = BlackKing;
7516         board[fromY][7] = EmptySquare;
7517         board[toY][4] = BlackRook;
7518     } else if (fromY == 7 && fromX == 3
7519                && board[fromY][fromX] == BlackKing
7520                && toY == 7 && toX == 1) {
7521         board[fromY][fromX] = EmptySquare;
7522         board[toY][toX] = BlackKing;
7523         board[fromY][0] = EmptySquare;
7524         board[toY][2] = BlackRook;
7525     } else if (board[fromY][fromX] == BlackPawn
7526                && toY == 0
7527                && gameInfo.variant != VariantXiangqi
7528                ) {
7529         /* black pawn promotion */
7530         board[0][toX] = CharToPiece(ToLower(promoChar));
7531         if (board[0][toX] == EmptySquare) {
7532             board[0][toX] = BlackQueen;
7533         }
7534         if(gameInfo.variant==VariantBughouse ||
7535            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7536             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7537         board[fromY][fromX] = EmptySquare;
7538     } else if ((fromY == 3)
7539                && (toX != fromX)
7540                && gameInfo.variant != VariantXiangqi
7541                && gameInfo.variant != VariantBerolina
7542                && (board[fromY][fromX] == BlackPawn)
7543                && (board[toY][toX] == EmptySquare)) {
7544         board[fromY][fromX] = EmptySquare;
7545         board[toY][toX] = BlackPawn;
7546         captured = board[toY + 1][toX];
7547         board[toY + 1][toX] = EmptySquare;
7548     } else if ((fromY == 3)
7549                && (toX == fromX)
7550                && gameInfo.variant == VariantBerolina
7551                && (board[fromY][fromX] == BlackPawn)
7552                && (board[toY][toX] == EmptySquare)) {
7553         board[fromY][fromX] = EmptySquare;
7554         board[toY][toX] = BlackPawn;
7555         if(oldEP & EP_BEROLIN_A) {
7556                 captured = board[fromY][fromX-1];
7557                 board[fromY][fromX-1] = EmptySquare;
7558         }else{  captured = board[fromY][fromX+1];
7559                 board[fromY][fromX+1] = EmptySquare;
7560         }
7561     } else {
7562         board[toY][toX] = board[fromY][fromX];
7563         board[fromY][fromX] = EmptySquare;
7564     }
7565
7566     /* [HGM] now we promote for Shogi, if needed */
7567     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7568         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7569   }
7570
7571     if (gameInfo.holdingsWidth != 0) {
7572
7573       /* !!A lot more code needs to be written to support holdings  */
7574       /* [HGM] OK, so I have written it. Holdings are stored in the */
7575       /* penultimate board files, so they are automaticlly stored   */
7576       /* in the game history.                                       */
7577       if (fromY == DROP_RANK) {
7578         /* Delete from holdings, by decreasing count */
7579         /* and erasing image if necessary            */
7580         p = (int) fromX;
7581         if(p < (int) BlackPawn) { /* white drop */
7582              p -= (int)WhitePawn;
7583                  p = PieceToNumber((ChessSquare)p);
7584              if(p >= gameInfo.holdingsSize) p = 0;
7585              if(--board[p][BOARD_WIDTH-2] <= 0)
7586                   board[p][BOARD_WIDTH-1] = EmptySquare;
7587              if((int)board[p][BOARD_WIDTH-2] < 0)
7588                         board[p][BOARD_WIDTH-2] = 0;
7589         } else {                  /* black drop */
7590              p -= (int)BlackPawn;
7591                  p = PieceToNumber((ChessSquare)p);
7592              if(p >= gameInfo.holdingsSize) p = 0;
7593              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7594                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7595              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7596                         board[BOARD_HEIGHT-1-p][1] = 0;
7597         }
7598       }
7599       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7600           && gameInfo.variant != VariantBughouse        ) {
7601         /* [HGM] holdings: Add to holdings, if holdings exist */
7602         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7603                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7604                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7605         }
7606         p = (int) captured;
7607         if (p >= (int) BlackPawn) {
7608           p -= (int)BlackPawn;
7609           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7610                   /* in Shogi restore piece to its original  first */
7611                   captured = (ChessSquare) (DEMOTED captured);
7612                   p = DEMOTED p;
7613           }
7614           p = PieceToNumber((ChessSquare)p);
7615           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7616           board[p][BOARD_WIDTH-2]++;
7617           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7618         } else {
7619           p -= (int)WhitePawn;
7620           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7621                   captured = (ChessSquare) (DEMOTED captured);
7622                   p = DEMOTED p;
7623           }
7624           p = PieceToNumber((ChessSquare)p);
7625           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7626           board[BOARD_HEIGHT-1-p][1]++;
7627           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7628         }
7629       }
7630     } else if (gameInfo.variant == VariantAtomic) {
7631       if (captured != EmptySquare) {
7632         int y, x;
7633         for (y = toY-1; y <= toY+1; y++) {
7634           for (x = toX-1; x <= toX+1; x++) {
7635             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7636                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7637               board[y][x] = EmptySquare;
7638             }
7639           }
7640         }
7641         board[toY][toX] = EmptySquare;
7642       }
7643     }
7644     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7645         /* [HGM] Shogi promotions */
7646         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7647     }
7648
7649     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7650                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7651         // [HGM] superchess: take promotion piece out of holdings
7652         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7653         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7654             if(!--board[k][BOARD_WIDTH-2])
7655                 board[k][BOARD_WIDTH-1] = EmptySquare;
7656         } else {
7657             if(!--board[BOARD_HEIGHT-1-k][1])
7658                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7659         }
7660     }
7661
7662 }
7663
7664 /* Updates forwardMostMove */
7665 void
7666 MakeMove(fromX, fromY, toX, toY, promoChar)
7667      int fromX, fromY, toX, toY;
7668      int promoChar;
7669 {
7670 //    forwardMostMove++; // [HGM] bare: moved downstream
7671
7672     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7673         int timeLeft; static int lastLoadFlag=0; int king, piece;
7674         piece = boards[forwardMostMove][fromY][fromX];
7675         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7676         if(gameInfo.variant == VariantKnightmate)
7677             king += (int) WhiteUnicorn - (int) WhiteKing;
7678         if(forwardMostMove == 0) {
7679             if(blackPlaysFirst) 
7680                 fprintf(serverMoves, "%s;", second.tidy);
7681             fprintf(serverMoves, "%s;", first.tidy);
7682             if(!blackPlaysFirst) 
7683                 fprintf(serverMoves, "%s;", second.tidy);
7684         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7685         lastLoadFlag = loadFlag;
7686         // print base move
7687         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7688         // print castling suffix
7689         if( toY == fromY && piece == king ) {
7690             if(toX-fromX > 1)
7691                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7692             if(fromX-toX >1)
7693                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7694         }
7695         // e.p. suffix
7696         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7697              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7698              boards[forwardMostMove][toY][toX] == EmptySquare
7699              && fromX != toX )
7700                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7701         // promotion suffix
7702         if(promoChar != NULLCHAR)
7703                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7704         if(!loadFlag) {
7705             fprintf(serverMoves, "/%d/%d",
7706                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7707             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7708             else                      timeLeft = blackTimeRemaining/1000;
7709             fprintf(serverMoves, "/%d", timeLeft);
7710         }
7711         fflush(serverMoves);
7712     }
7713
7714     if (forwardMostMove+1 >= MAX_MOVES) {
7715       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7716                         0, 1);
7717       return;
7718     }
7719     if (commentList[forwardMostMove+1] != NULL) {
7720         free(commentList[forwardMostMove+1]);
7721         commentList[forwardMostMove+1] = NULL;
7722     }
7723     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7724     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7725     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7726                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7727     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7728     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7729     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7730     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7731     gameInfo.result = GameUnfinished;
7732     if (gameInfo.resultDetails != NULL) {
7733         free(gameInfo.resultDetails);
7734         gameInfo.resultDetails = NULL;
7735     }
7736     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7737                               moveList[forwardMostMove - 1]);
7738     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7739                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7740                              fromY, fromX, toY, toX, promoChar,
7741                              parseList[forwardMostMove - 1]);
7742     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7743                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7744                             castlingRights[forwardMostMove]) ) {
7745       case MT_NONE:
7746       case MT_STALEMATE:
7747       default:
7748         break;
7749       case MT_CHECK:
7750         if(gameInfo.variant != VariantShogi)
7751             strcat(parseList[forwardMostMove - 1], "+");
7752         break;
7753       case MT_CHECKMATE:
7754       case MT_STAINMATE:
7755         strcat(parseList[forwardMostMove - 1], "#");
7756         break;
7757     }
7758     if (appData.debugMode) {
7759         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7760     }
7761
7762 }
7763
7764 /* Updates currentMove if not pausing */
7765 void
7766 ShowMove(fromX, fromY, toX, toY)
7767 {
7768     int instant = (gameMode == PlayFromGameFile) ?
7769         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7770     if(appData.noGUI) return;
7771     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7772         if (!instant) {
7773             if (forwardMostMove == currentMove + 1) {
7774                 AnimateMove(boards[forwardMostMove - 1],
7775                             fromX, fromY, toX, toY);
7776             }
7777             if (appData.highlightLastMove) {
7778                 SetHighlights(fromX, fromY, toX, toY);
7779             }
7780         }
7781         currentMove = forwardMostMove;
7782     }
7783
7784     if (instant) return;
7785
7786     DisplayMove(currentMove - 1);
7787     DrawPosition(FALSE, boards[currentMove]);
7788     DisplayBothClocks();
7789     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7790 }
7791
7792 void SendEgtPath(ChessProgramState *cps)
7793 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7794         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7795
7796         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7797
7798         while(*p) {
7799             char c, *q = name+1, *r, *s;
7800
7801             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7802             while(*p && *p != ',') *q++ = *p++;
7803             *q++ = ':'; *q = 0;
7804             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7805                 strcmp(name, ",nalimov:") == 0 ) {
7806                 // take nalimov path from the menu-changeable option first, if it is defined
7807                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7808                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7809             } else
7810             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7811                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7812                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7813                 s = r = StrStr(s, ":") + 1; // beginning of path info
7814                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7815                 c = *r; *r = 0;             // temporarily null-terminate path info
7816                     *--q = 0;               // strip of trailig ':' from name
7817                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7818                 *r = c;
7819                 SendToProgram(buf,cps);     // send egtbpath command for this format
7820             }
7821             if(*p == ',') p++; // read away comma to position for next format name
7822         }
7823 }
7824
7825 void
7826 InitChessProgram(cps, setup)
7827      ChessProgramState *cps;
7828      int setup; /* [HGM] needed to setup FRC opening position */
7829 {
7830     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7831     if (appData.noChessProgram) return;
7832     hintRequested = FALSE;
7833     bookRequested = FALSE;
7834
7835     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7836     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7837     if(cps->memSize) { /* [HGM] memory */
7838         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7839         SendToProgram(buf, cps);
7840     }
7841     SendEgtPath(cps); /* [HGM] EGT */
7842     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7843         sprintf(buf, "cores %d\n", appData.smpCores);
7844         SendToProgram(buf, cps);
7845     }
7846
7847     SendToProgram(cps->initString, cps);
7848     if (gameInfo.variant != VariantNormal &&
7849         gameInfo.variant != VariantLoadable
7850         /* [HGM] also send variant if board size non-standard */
7851         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7852                                             ) {
7853       char *v = VariantName(gameInfo.variant);
7854       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7855         /* [HGM] in protocol 1 we have to assume all variants valid */
7856         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7857         DisplayFatalError(buf, 0, 1);
7858         return;
7859       }
7860
7861       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7862       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863       if( gameInfo.variant == VariantXiangqi )
7864            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7865       if( gameInfo.variant == VariantShogi )
7866            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7867       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7868            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7869       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7870                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7871            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7872       if( gameInfo.variant == VariantCourier )
7873            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7874       if( gameInfo.variant == VariantSuper )
7875            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7876       if( gameInfo.variant == VariantGreat )
7877            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7878
7879       if(overruled) {
7880            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7881                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7882            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7883            if(StrStr(cps->variants, b) == NULL) { 
7884                // specific sized variant not known, check if general sizing allowed
7885                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7886                    if(StrStr(cps->variants, "boardsize") == NULL) {
7887                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7888                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7889                        DisplayFatalError(buf, 0, 1);
7890                        return;
7891                    }
7892                    /* [HGM] here we really should compare with the maximum supported board size */
7893                }
7894            }
7895       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7896       sprintf(buf, "variant %s\n", b);
7897       SendToProgram(buf, cps);
7898     }
7899     currentlyInitializedVariant = gameInfo.variant;
7900
7901     /* [HGM] send opening position in FRC to first engine */
7902     if(setup) {
7903           SendToProgram("force\n", cps);
7904           SendBoard(cps, 0);
7905           /* engine is now in force mode! Set flag to wake it up after first move. */
7906           setboardSpoiledMachineBlack = 1;
7907     }
7908
7909     if (cps->sendICS) {
7910       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7911       SendToProgram(buf, cps);
7912     }
7913     cps->maybeThinking = FALSE;
7914     cps->offeredDraw = 0;
7915     if (!appData.icsActive) {
7916         SendTimeControl(cps, movesPerSession, timeControl,
7917                         timeIncrement, appData.searchDepth,
7918                         searchTime);
7919     }
7920     if (appData.showThinking 
7921         // [HGM] thinking: four options require thinking output to be sent
7922         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7923                                 ) {
7924         SendToProgram("post\n", cps);
7925     }
7926     SendToProgram("hard\n", cps);
7927     if (!appData.ponderNextMove) {
7928         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7929            it without being sure what state we are in first.  "hard"
7930            is not a toggle, so that one is OK.
7931          */
7932         SendToProgram("easy\n", cps);
7933     }
7934     if (cps->usePing) {
7935       sprintf(buf, "ping %d\n", ++cps->lastPing);
7936       SendToProgram(buf, cps);
7937     }
7938     cps->initDone = TRUE;
7939 }   
7940
7941
7942 void
7943 StartChessProgram(cps)
7944      ChessProgramState *cps;
7945 {
7946     char buf[MSG_SIZ];
7947     int err;
7948
7949     if (appData.noChessProgram) return;
7950     cps->initDone = FALSE;
7951
7952     if (strcmp(cps->host, "localhost") == 0) {
7953         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7954     } else if (*appData.remoteShell == NULLCHAR) {
7955         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7956     } else {
7957         if (*appData.remoteUser == NULLCHAR) {
7958           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7959                     cps->program);
7960         } else {
7961           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7962                     cps->host, appData.remoteUser, cps->program);
7963         }
7964         err = StartChildProcess(buf, "", &cps->pr);
7965     }
7966     
7967     if (err != 0) {
7968         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7969         DisplayFatalError(buf, err, 1);
7970         cps->pr = NoProc;
7971         cps->isr = NULL;
7972         return;
7973     }
7974     
7975     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7976     if (cps->protocolVersion > 1) {
7977       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7978       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7979       cps->comboCnt = 0;  //                and values of combo boxes
7980       SendToProgram(buf, cps);
7981     } else {
7982       SendToProgram("xboard\n", cps);
7983     }
7984 }
7985
7986
7987 void
7988 TwoMachinesEventIfReady P((void))
7989 {
7990   if (first.lastPing != first.lastPong) {
7991     DisplayMessage("", _("Waiting for first chess program"));
7992     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7993     return;
7994   }
7995   if (second.lastPing != second.lastPong) {
7996     DisplayMessage("", _("Waiting for second chess program"));
7997     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7998     return;
7999   }
8000   ThawUI();
8001   TwoMachinesEvent();
8002 }
8003
8004 void
8005 NextMatchGame P((void))
8006 {
8007     int index; /* [HGM] autoinc: step load index during match */
8008     Reset(FALSE, TRUE);
8009     if (*appData.loadGameFile != NULLCHAR) {
8010         index = appData.loadGameIndex;
8011         if(index < 0) { // [HGM] autoinc
8012             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8013             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8014         } 
8015         LoadGameFromFile(appData.loadGameFile,
8016                          index,
8017                          appData.loadGameFile, FALSE);
8018     } else if (*appData.loadPositionFile != NULLCHAR) {
8019         index = appData.loadPositionIndex;
8020         if(index < 0) { // [HGM] autoinc
8021             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8022             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8023         } 
8024         LoadPositionFromFile(appData.loadPositionFile,
8025                              index,
8026                              appData.loadPositionFile);
8027     }
8028     TwoMachinesEventIfReady();
8029 }
8030
8031 void UserAdjudicationEvent( int result )
8032 {
8033     ChessMove gameResult = GameIsDrawn;
8034
8035     if( result > 0 ) {
8036         gameResult = WhiteWins;
8037     }
8038     else if( result < 0 ) {
8039         gameResult = BlackWins;
8040     }
8041
8042     if( gameMode == TwoMachinesPlay ) {
8043         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8044     }
8045 }
8046
8047
8048 // [HGM] save: calculate checksum of game to make games easily identifiable
8049 int StringCheckSum(char *s)
8050 {
8051         int i = 0;
8052         if(s==NULL) return 0;
8053         while(*s) i = i*259 + *s++;
8054         return i;
8055 }
8056
8057 int GameCheckSum()
8058 {
8059         int i, sum=0;
8060         for(i=backwardMostMove; i<forwardMostMove; i++) {
8061                 sum += pvInfoList[i].depth;
8062                 sum += StringCheckSum(parseList[i]);
8063                 sum += StringCheckSum(commentList[i]);
8064                 sum *= 261;
8065         }
8066         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8067         return sum + StringCheckSum(commentList[i]);
8068 } // end of save patch
8069
8070 void
8071 GameEnds(result, resultDetails, whosays)
8072      ChessMove result;
8073      char *resultDetails;
8074      int whosays;
8075 {
8076     GameMode nextGameMode;
8077     int isIcsGame;
8078     char buf[MSG_SIZ];
8079
8080     if(endingGame) return; /* [HGM] crash: forbid recursion */
8081     endingGame = 1;
8082
8083     if (appData.debugMode) {
8084       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8085               result, resultDetails ? resultDetails : "(null)", whosays);
8086     }
8087
8088     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8089         /* If we are playing on ICS, the server decides when the
8090            game is over, but the engine can offer to draw, claim 
8091            a draw, or resign. 
8092          */
8093 #if ZIPPY
8094         if (appData.zippyPlay && first.initDone) {
8095             if (result == GameIsDrawn) {
8096                 /* In case draw still needs to be claimed */
8097                 SendToICS(ics_prefix);
8098                 SendToICS("draw\n");
8099             } else if (StrCaseStr(resultDetails, "resign")) {
8100                 SendToICS(ics_prefix);
8101                 SendToICS("resign\n");
8102             }
8103         }
8104 #endif
8105         endingGame = 0; /* [HGM] crash */
8106         return;
8107     }
8108
8109     /* If we're loading the game from a file, stop */
8110     if (whosays == GE_FILE) {
8111       (void) StopLoadGameTimer();
8112       gameFileFP = NULL;
8113     }
8114
8115     /* Cancel draw offers */
8116     first.offeredDraw = second.offeredDraw = 0;
8117
8118     /* If this is an ICS game, only ICS can really say it's done;
8119        if not, anyone can. */
8120     isIcsGame = (gameMode == IcsPlayingWhite || 
8121                  gameMode == IcsPlayingBlack || 
8122                  gameMode == IcsObserving    || 
8123                  gameMode == IcsExamining);
8124
8125     if (!isIcsGame || whosays == GE_ICS) {
8126         /* OK -- not an ICS game, or ICS said it was done */
8127         StopClocks();
8128         if (!isIcsGame && !appData.noChessProgram) 
8129           SetUserThinkingEnables();
8130     
8131         /* [HGM] if a machine claims the game end we verify this claim */
8132         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8133             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8134                 char claimer;
8135                 ChessMove trueResult = (ChessMove) -1;
8136
8137                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8138                                             first.twoMachinesColor[0] :
8139                                             second.twoMachinesColor[0] ;
8140
8141                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8142                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8143                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8144                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8145                 } else
8146                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8147                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8148                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8149                 } else
8150                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8151                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8152                 }
8153
8154                 // now verify win claims, but not in drop games, as we don't understand those yet
8155                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8156                                                  || gameInfo.variant == VariantGreat) &&
8157                     (result == WhiteWins && claimer == 'w' ||
8158                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8159                       if (appData.debugMode) {
8160                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8161                                 result, epStatus[forwardMostMove], forwardMostMove);
8162                       }
8163                       if(result != trueResult) {
8164                               sprintf(buf, "False win claim: '%s'", resultDetails);
8165                               result = claimer == 'w' ? BlackWins : WhiteWins;
8166                               resultDetails = buf;
8167                       }
8168                 } else
8169                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8170                     && (forwardMostMove <= backwardMostMove ||
8171                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8172                         (claimer=='b')==(forwardMostMove&1))
8173                                                                                   ) {
8174                       /* [HGM] verify: draws that were not flagged are false claims */
8175                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8176                       result = claimer == 'w' ? BlackWins : WhiteWins;
8177                       resultDetails = buf;
8178                 }
8179                 /* (Claiming a loss is accepted no questions asked!) */
8180             }
8181             /* [HGM] bare: don't allow bare King to win */
8182             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8183                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8184                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8185                && result != GameIsDrawn)
8186             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8187                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8188                         int p = (int)boards[forwardMostMove][i][j] - color;
8189                         if(p >= 0 && p <= (int)WhiteKing) k++;
8190                 }
8191                 if (appData.debugMode) {
8192                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8193                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8194                 }
8195                 if(k <= 1) {
8196                         result = GameIsDrawn;
8197                         sprintf(buf, "%s but bare king", resultDetails);
8198                         resultDetails = buf;
8199                 }
8200             }
8201         }
8202
8203
8204         if(serverMoves != NULL && !loadFlag) { char c = '=';
8205             if(result==WhiteWins) c = '+';
8206             if(result==BlackWins) c = '-';
8207             if(resultDetails != NULL)
8208                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8209         }
8210         if (resultDetails != NULL) {
8211             gameInfo.result = result;
8212             gameInfo.resultDetails = StrSave(resultDetails);
8213
8214             /* display last move only if game was not loaded from file */
8215             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8216                 DisplayMove(currentMove - 1);
8217     
8218             if (forwardMostMove != 0) {
8219                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8220                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8221                                                                 ) {
8222                     if (*appData.saveGameFile != NULLCHAR) {
8223                         SaveGameToFile(appData.saveGameFile, TRUE);
8224                     } else if (appData.autoSaveGames) {
8225                         AutoSaveGame();
8226                     }
8227                     if (*appData.savePositionFile != NULLCHAR) {
8228                         SavePositionToFile(appData.savePositionFile);
8229                     }
8230                 }
8231             }
8232
8233             /* Tell program how game ended in case it is learning */
8234             /* [HGM] Moved this to after saving the PGN, just in case */
8235             /* engine died and we got here through time loss. In that */
8236             /* case we will get a fatal error writing the pipe, which */
8237             /* would otherwise lose us the PGN.                       */
8238             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8239             /* output during GameEnds should never be fatal anymore   */
8240             if (gameMode == MachinePlaysWhite ||
8241                 gameMode == MachinePlaysBlack ||
8242                 gameMode == TwoMachinesPlay ||
8243                 gameMode == IcsPlayingWhite ||
8244                 gameMode == IcsPlayingBlack ||
8245                 gameMode == BeginningOfGame) {
8246                 char buf[MSG_SIZ];
8247                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8248                         resultDetails);
8249                 if (first.pr != NoProc) {
8250                     SendToProgram(buf, &first);
8251                 }
8252                 if (second.pr != NoProc &&
8253                     gameMode == TwoMachinesPlay) {
8254                     SendToProgram(buf, &second);
8255                 }
8256             }
8257         }
8258
8259         if (appData.icsActive) {
8260             if (appData.quietPlay &&
8261                 (gameMode == IcsPlayingWhite ||
8262                  gameMode == IcsPlayingBlack)) {
8263                 SendToICS(ics_prefix);
8264                 SendToICS("set shout 1\n");
8265             }
8266             nextGameMode = IcsIdle;
8267             ics_user_moved = FALSE;
8268             /* clean up premove.  It's ugly when the game has ended and the
8269              * premove highlights are still on the board.
8270              */
8271             if (gotPremove) {
8272               gotPremove = FALSE;
8273               ClearPremoveHighlights();
8274               DrawPosition(FALSE, boards[currentMove]);
8275             }
8276             if (whosays == GE_ICS) {
8277                 switch (result) {
8278                 case WhiteWins:
8279                     if (gameMode == IcsPlayingWhite)
8280                         PlayIcsWinSound();
8281                     else if(gameMode == IcsPlayingBlack)
8282                         PlayIcsLossSound();
8283                     break;
8284                 case BlackWins:
8285                     if (gameMode == IcsPlayingBlack)
8286                         PlayIcsWinSound();
8287                     else if(gameMode == IcsPlayingWhite)
8288                         PlayIcsLossSound();
8289                     break;
8290                 case GameIsDrawn:
8291                     PlayIcsDrawSound();
8292                     break;
8293                 default:
8294                     PlayIcsUnfinishedSound();
8295                 }
8296             }
8297         } else if (gameMode == EditGame ||
8298                    gameMode == PlayFromGameFile || 
8299                    gameMode == AnalyzeMode || 
8300                    gameMode == AnalyzeFile) {
8301             nextGameMode = gameMode;
8302         } else {
8303             nextGameMode = EndOfGame;
8304         }
8305         pausing = FALSE;
8306         ModeHighlight();
8307     } else {
8308         nextGameMode = gameMode;
8309     }
8310
8311     if (appData.noChessProgram) {
8312         gameMode = nextGameMode;
8313         ModeHighlight();
8314         endingGame = 0; /* [HGM] crash */
8315         return;
8316     }
8317
8318     if (first.reuse) {
8319         /* Put first chess program into idle state */
8320         if (first.pr != NoProc &&
8321             (gameMode == MachinePlaysWhite ||
8322              gameMode == MachinePlaysBlack ||
8323              gameMode == TwoMachinesPlay ||
8324              gameMode == IcsPlayingWhite ||
8325              gameMode == IcsPlayingBlack ||
8326              gameMode == BeginningOfGame)) {
8327             SendToProgram("force\n", &first);
8328             if (first.usePing) {
8329               char buf[MSG_SIZ];
8330               sprintf(buf, "ping %d\n", ++first.lastPing);
8331               SendToProgram(buf, &first);
8332             }
8333         }
8334     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8335         /* Kill off first chess program */
8336         if (first.isr != NULL)
8337           RemoveInputSource(first.isr);
8338         first.isr = NULL;
8339     
8340         if (first.pr != NoProc) {
8341             ExitAnalyzeMode();
8342             DoSleep( appData.delayBeforeQuit );
8343             SendToProgram("quit\n", &first);
8344             DoSleep( appData.delayAfterQuit );
8345             DestroyChildProcess(first.pr, first.useSigterm);
8346         }
8347         first.pr = NoProc;
8348     }
8349     if (second.reuse) {
8350         /* Put second chess program into idle state */
8351         if (second.pr != NoProc &&
8352             gameMode == TwoMachinesPlay) {
8353             SendToProgram("force\n", &second);
8354             if (second.usePing) {
8355               char buf[MSG_SIZ];
8356               sprintf(buf, "ping %d\n", ++second.lastPing);
8357               SendToProgram(buf, &second);
8358             }
8359         }
8360     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8361         /* Kill off second chess program */
8362         if (second.isr != NULL)
8363           RemoveInputSource(second.isr);
8364         second.isr = NULL;
8365     
8366         if (second.pr != NoProc) {
8367             DoSleep( appData.delayBeforeQuit );
8368             SendToProgram("quit\n", &second);
8369             DoSleep( appData.delayAfterQuit );
8370             DestroyChildProcess(second.pr, second.useSigterm);
8371         }
8372         second.pr = NoProc;
8373     }
8374
8375     if (matchMode && gameMode == TwoMachinesPlay) {
8376         switch (result) {
8377         case WhiteWins:
8378           if (first.twoMachinesColor[0] == 'w') {
8379             first.matchWins++;
8380           } else {
8381             second.matchWins++;
8382           }
8383           break;
8384         case BlackWins:
8385           if (first.twoMachinesColor[0] == 'b') {
8386             first.matchWins++;
8387           } else {
8388             second.matchWins++;
8389           }
8390           break;
8391         default:
8392           break;
8393         }
8394         if (matchGame < appData.matchGames) {
8395             char *tmp;
8396             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8397                 tmp = first.twoMachinesColor;
8398                 first.twoMachinesColor = second.twoMachinesColor;
8399                 second.twoMachinesColor = tmp;
8400             }
8401             gameMode = nextGameMode;
8402             matchGame++;
8403             if(appData.matchPause>10000 || appData.matchPause<10)
8404                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8405             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8406             endingGame = 0; /* [HGM] crash */
8407             return;
8408         } else {
8409             char buf[MSG_SIZ];
8410             gameMode = nextGameMode;
8411             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8412                     first.tidy, second.tidy,
8413                     first.matchWins, second.matchWins,
8414                     appData.matchGames - (first.matchWins + second.matchWins));
8415             DisplayFatalError(buf, 0, 0);
8416         }
8417     }
8418     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8419         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8420       ExitAnalyzeMode();
8421     gameMode = nextGameMode;
8422     ModeHighlight();
8423     endingGame = 0;  /* [HGM] crash */
8424 }
8425
8426 /* Assumes program was just initialized (initString sent).
8427    Leaves program in force mode. */
8428 void
8429 FeedMovesToProgram(cps, upto) 
8430      ChessProgramState *cps;
8431      int upto;
8432 {
8433     int i;
8434     
8435     if (appData.debugMode)
8436       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8437               startedFromSetupPosition ? "position and " : "",
8438               backwardMostMove, upto, cps->which);
8439     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8440         // [HGM] variantswitch: make engine aware of new variant
8441         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8442                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8443         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8444         SendToProgram(buf, cps);
8445         currentlyInitializedVariant = gameInfo.variant;
8446     }
8447     SendToProgram("force\n", cps);
8448     if (startedFromSetupPosition) {
8449         SendBoard(cps, backwardMostMove);
8450     if (appData.debugMode) {
8451         fprintf(debugFP, "feedMoves\n");
8452     }
8453     }
8454     for (i = backwardMostMove; i < upto; i++) {
8455         SendMoveToProgram(i, cps);
8456     }
8457 }
8458
8459
8460 void
8461 ResurrectChessProgram()
8462 {
8463      /* The chess program may have exited.
8464         If so, restart it and feed it all the moves made so far. */
8465
8466     if (appData.noChessProgram || first.pr != NoProc) return;
8467     
8468     StartChessProgram(&first);
8469     InitChessProgram(&first, FALSE);
8470     FeedMovesToProgram(&first, currentMove);
8471
8472     if (!first.sendTime) {
8473         /* can't tell gnuchess what its clock should read,
8474            so we bow to its notion. */
8475         ResetClocks();
8476         timeRemaining[0][currentMove] = whiteTimeRemaining;
8477         timeRemaining[1][currentMove] = blackTimeRemaining;
8478     }
8479
8480     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8481                 appData.icsEngineAnalyze) && first.analysisSupport) {
8482       SendToProgram("analyze\n", &first);
8483       first.analyzing = TRUE;
8484     }
8485 }
8486
8487 /*
8488  * Button procedures
8489  */
8490 void
8491 Reset(redraw, init)
8492      int redraw, init;
8493 {
8494     int i;
8495
8496     if (appData.debugMode) {
8497         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8498                 redraw, init, gameMode);
8499     }
8500     pausing = pauseExamInvalid = FALSE;
8501     startedFromSetupPosition = blackPlaysFirst = FALSE;
8502     firstMove = TRUE;
8503     whiteFlag = blackFlag = FALSE;
8504     userOfferedDraw = FALSE;
8505     hintRequested = bookRequested = FALSE;
8506     first.maybeThinking = FALSE;
8507     second.maybeThinking = FALSE;
8508     first.bookSuspend = FALSE; // [HGM] book
8509     second.bookSuspend = FALSE;
8510     thinkOutput[0] = NULLCHAR;
8511     lastHint[0] = NULLCHAR;
8512     ClearGameInfo(&gameInfo);
8513     gameInfo.variant = StringToVariant(appData.variant);
8514     ics_user_moved = ics_clock_paused = FALSE;
8515     ics_getting_history = H_FALSE;
8516     ics_gamenum = -1;
8517     white_holding[0] = black_holding[0] = NULLCHAR;
8518     ClearProgramStats();
8519     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8520     
8521     ResetFrontEnd();
8522     ClearHighlights();
8523     flipView = appData.flipView;
8524     ClearPremoveHighlights();
8525     gotPremove = FALSE;
8526     alarmSounded = FALSE;
8527
8528     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8529     if(appData.serverMovesName != NULL) {
8530         /* [HGM] prepare to make moves file for broadcasting */
8531         clock_t t = clock();
8532         if(serverMoves != NULL) fclose(serverMoves);
8533         serverMoves = fopen(appData.serverMovesName, "r");
8534         if(serverMoves != NULL) {
8535             fclose(serverMoves);
8536             /* delay 15 sec before overwriting, so all clients can see end */
8537             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8538         }
8539         serverMoves = fopen(appData.serverMovesName, "w");
8540     }
8541
8542     ExitAnalyzeMode();
8543     gameMode = BeginningOfGame;
8544     ModeHighlight();
8545     if(appData.icsActive) gameInfo.variant = VariantNormal;
8546     currentMove = forwardMostMove = backwardMostMove = 0;
8547     InitPosition(redraw);
8548     for (i = 0; i < MAX_MOVES; i++) {
8549         if (commentList[i] != NULL) {
8550             free(commentList[i]);
8551             commentList[i] = NULL;
8552         }
8553     }
8554     ResetClocks();
8555     timeRemaining[0][0] = whiteTimeRemaining;
8556     timeRemaining[1][0] = blackTimeRemaining;
8557     if (first.pr == NULL) {
8558         StartChessProgram(&first);
8559     }
8560     if (init) {
8561             InitChessProgram(&first, startedFromSetupPosition);
8562     }
8563     DisplayTitle("");
8564     DisplayMessage("", "");
8565     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8566     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8567 }
8568
8569 void
8570 AutoPlayGameLoop()
8571 {
8572     for (;;) {
8573         if (!AutoPlayOneMove())
8574           return;
8575         if (matchMode || appData.timeDelay == 0)
8576           continue;
8577         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8578           return;
8579         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8580         break;
8581     }
8582 }
8583
8584
8585 int
8586 AutoPlayOneMove()
8587 {
8588     int fromX, fromY, toX, toY;
8589
8590     if (appData.debugMode) {
8591       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8592     }
8593
8594     if (gameMode != PlayFromGameFile)
8595       return FALSE;
8596
8597     if (currentMove >= forwardMostMove) {
8598       gameMode = EditGame;
8599       ModeHighlight();
8600
8601       /* [AS] Clear current move marker at the end of a game */
8602       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8603
8604       return FALSE;
8605     }
8606     
8607     toX = moveList[currentMove][2] - AAA;
8608     toY = moveList[currentMove][3] - ONE;
8609
8610     if (moveList[currentMove][1] == '@') {
8611         if (appData.highlightLastMove) {
8612             SetHighlights(-1, -1, toX, toY);
8613         }
8614     } else {
8615         fromX = moveList[currentMove][0] - AAA;
8616         fromY = moveList[currentMove][1] - ONE;
8617
8618         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8619
8620         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8621
8622         if (appData.highlightLastMove) {
8623             SetHighlights(fromX, fromY, toX, toY);
8624         }
8625     }
8626     DisplayMove(currentMove);
8627     SendMoveToProgram(currentMove++, &first);
8628     DisplayBothClocks();
8629     DrawPosition(FALSE, boards[currentMove]);
8630     // [HGM] PV info: always display, routine tests if empty
8631     DisplayComment(currentMove - 1, commentList[currentMove]);
8632     return TRUE;
8633 }
8634
8635
8636 int
8637 LoadGameOneMove(readAhead)
8638      ChessMove readAhead;
8639 {
8640     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8641     char promoChar = NULLCHAR;
8642     ChessMove moveType;
8643     char move[MSG_SIZ];
8644     char *p, *q;
8645     
8646     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8647         gameMode != AnalyzeMode && gameMode != Training) {
8648         gameFileFP = NULL;
8649         return FALSE;
8650     }
8651     
8652     yyboardindex = forwardMostMove;
8653     if (readAhead != (ChessMove)0) {
8654       moveType = readAhead;
8655     } else {
8656       if (gameFileFP == NULL)
8657           return FALSE;
8658       moveType = (ChessMove) yylex();
8659     }
8660     
8661     done = FALSE;
8662     switch (moveType) {
8663       case Comment:
8664         if (appData.debugMode) 
8665           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8666         p = yy_text;
8667         if (*p == '{' || *p == '[' || *p == '(') {
8668             p[strlen(p) - 1] = NULLCHAR;
8669             p++;
8670         }
8671
8672         /* append the comment but don't display it */
8673         while (*p == '\n') p++;
8674         AppendComment(currentMove, p);
8675         return TRUE;
8676
8677       case WhiteCapturesEnPassant:
8678       case BlackCapturesEnPassant:
8679       case WhitePromotionChancellor:
8680       case BlackPromotionChancellor:
8681       case WhitePromotionArchbishop:
8682       case BlackPromotionArchbishop:
8683       case WhitePromotionCentaur:
8684       case BlackPromotionCentaur:
8685       case WhitePromotionQueen:
8686       case BlackPromotionQueen:
8687       case WhitePromotionRook:
8688       case BlackPromotionRook:
8689       case WhitePromotionBishop:
8690       case BlackPromotionBishop:
8691       case WhitePromotionKnight:
8692       case BlackPromotionKnight:
8693       case WhitePromotionKing:
8694       case BlackPromotionKing:
8695       case NormalMove:
8696       case WhiteKingSideCastle:
8697       case WhiteQueenSideCastle:
8698       case BlackKingSideCastle:
8699       case BlackQueenSideCastle:
8700       case WhiteKingSideCastleWild:
8701       case WhiteQueenSideCastleWild:
8702       case BlackKingSideCastleWild:
8703       case BlackQueenSideCastleWild:
8704       /* PUSH Fabien */
8705       case WhiteHSideCastleFR:
8706       case WhiteASideCastleFR:
8707       case BlackHSideCastleFR:
8708       case BlackASideCastleFR:
8709       /* POP Fabien */
8710         if (appData.debugMode)
8711           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8712         fromX = currentMoveString[0] - AAA;
8713         fromY = currentMoveString[1] - ONE;
8714         toX = currentMoveString[2] - AAA;
8715         toY = currentMoveString[3] - ONE;
8716         promoChar = currentMoveString[4];
8717         break;
8718
8719       case WhiteDrop:
8720       case BlackDrop:
8721         if (appData.debugMode)
8722           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8723         fromX = moveType == WhiteDrop ?
8724           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8725         (int) CharToPiece(ToLower(currentMoveString[0]));
8726         fromY = DROP_RANK;
8727         toX = currentMoveString[2] - AAA;
8728         toY = currentMoveString[3] - ONE;
8729         break;
8730
8731       case WhiteWins:
8732       case BlackWins:
8733       case GameIsDrawn:
8734       case GameUnfinished:
8735         if (appData.debugMode)
8736           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8737         p = strchr(yy_text, '{');
8738         if (p == NULL) p = strchr(yy_text, '(');
8739         if (p == NULL) {
8740             p = yy_text;
8741             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8742         } else {
8743             q = strchr(p, *p == '{' ? '}' : ')');
8744             if (q != NULL) *q = NULLCHAR;
8745             p++;
8746         }
8747         GameEnds(moveType, p, GE_FILE);
8748         done = TRUE;
8749         if (cmailMsgLoaded) {
8750             ClearHighlights();
8751             flipView = WhiteOnMove(currentMove);
8752             if (moveType == GameUnfinished) flipView = !flipView;
8753             if (appData.debugMode)
8754               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8755         }
8756         break;
8757
8758       case (ChessMove) 0:       /* end of file */
8759         if (appData.debugMode)
8760           fprintf(debugFP, "Parser hit end of file\n");
8761         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8762                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8763           case MT_NONE:
8764           case MT_CHECK:
8765             break;
8766           case MT_CHECKMATE:
8767           case MT_STAINMATE:
8768             if (WhiteOnMove(currentMove)) {
8769                 GameEnds(BlackWins, "Black mates", GE_FILE);
8770             } else {
8771                 GameEnds(WhiteWins, "White mates", GE_FILE);
8772             }
8773             break;
8774           case MT_STALEMATE:
8775             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8776             break;
8777         }
8778         done = TRUE;
8779         break;
8780
8781       case MoveNumberOne:
8782         if (lastLoadGameStart == GNUChessGame) {
8783             /* GNUChessGames have numbers, but they aren't move numbers */
8784             if (appData.debugMode)
8785               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8786                       yy_text, (int) moveType);
8787             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8788         }
8789         /* else fall thru */
8790
8791       case XBoardGame:
8792       case GNUChessGame:
8793       case PGNTag:
8794         /* Reached start of next game in file */
8795         if (appData.debugMode)
8796           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8797         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8798                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8799           case MT_NONE:
8800           case MT_CHECK:
8801             break;
8802           case MT_CHECKMATE:
8803           case MT_STAINMATE:
8804             if (WhiteOnMove(currentMove)) {
8805                 GameEnds(BlackWins, "Black mates", GE_FILE);
8806             } else {
8807                 GameEnds(WhiteWins, "White mates", GE_FILE);
8808             }
8809             break;
8810           case MT_STALEMATE:
8811             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8812             break;
8813         }
8814         done = TRUE;
8815         break;
8816
8817       case PositionDiagram:     /* should not happen; ignore */
8818       case ElapsedTime:         /* ignore */
8819       case NAG:                 /* ignore */
8820         if (appData.debugMode)
8821           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8822                   yy_text, (int) moveType);
8823         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8824
8825       case IllegalMove:
8826         if (appData.testLegality) {
8827             if (appData.debugMode)
8828               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8829             sprintf(move, _("Illegal move: %d.%s%s"),
8830                     (forwardMostMove / 2) + 1,
8831                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8832             DisplayError(move, 0);
8833             done = TRUE;
8834         } else {
8835             if (appData.debugMode)
8836               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8837                       yy_text, currentMoveString);
8838             fromX = currentMoveString[0] - AAA;
8839             fromY = currentMoveString[1] - ONE;
8840             toX = currentMoveString[2] - AAA;
8841             toY = currentMoveString[3] - ONE;
8842             promoChar = currentMoveString[4];
8843         }
8844         break;
8845
8846       case AmbiguousMove:
8847         if (appData.debugMode)
8848           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8849         sprintf(move, _("Ambiguous move: %d.%s%s"),
8850                 (forwardMostMove / 2) + 1,
8851                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8852         DisplayError(move, 0);
8853         done = TRUE;
8854         break;
8855
8856       default:
8857       case ImpossibleMove:
8858         if (appData.debugMode)
8859           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8860         sprintf(move, _("Illegal move: %d.%s%s"),
8861                 (forwardMostMove / 2) + 1,
8862                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8863         DisplayError(move, 0);
8864         done = TRUE;
8865         break;
8866     }
8867
8868     if (done) {
8869         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8870             DrawPosition(FALSE, boards[currentMove]);
8871             DisplayBothClocks();
8872             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8873               DisplayComment(currentMove - 1, commentList[currentMove]);
8874         }
8875         (void) StopLoadGameTimer();
8876         gameFileFP = NULL;
8877         cmailOldMove = forwardMostMove;
8878         return FALSE;
8879     } else {
8880         /* currentMoveString is set as a side-effect of yylex */
8881         strcat(currentMoveString, "\n");
8882         strcpy(moveList[forwardMostMove], currentMoveString);
8883         
8884         thinkOutput[0] = NULLCHAR;
8885         MakeMove(fromX, fromY, toX, toY, promoChar);
8886         currentMove = forwardMostMove;
8887         return TRUE;
8888     }
8889 }
8890
8891 /* Load the nth game from the given file */
8892 int
8893 LoadGameFromFile(filename, n, title, useList)
8894      char *filename;
8895      int n;
8896      char *title;
8897      /*Boolean*/ int useList;
8898 {
8899     FILE *f;
8900     char buf[MSG_SIZ];
8901
8902     if (strcmp(filename, "-") == 0) {
8903         f = stdin;
8904         title = "stdin";
8905     } else {
8906         f = fopen(filename, "rb");
8907         if (f == NULL) {
8908           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8909             DisplayError(buf, errno);
8910             return FALSE;
8911         }
8912     }
8913     if (fseek(f, 0, 0) == -1) {
8914         /* f is not seekable; probably a pipe */
8915         useList = FALSE;
8916     }
8917     if (useList && n == 0) {
8918         int error = GameListBuild(f);
8919         if (error) {
8920             DisplayError(_("Cannot build game list"), error);
8921         } else if (!ListEmpty(&gameList) &&
8922                    ((ListGame *) gameList.tailPred)->number > 1) {
8923             GameListPopUp(f, title);
8924             return TRUE;
8925         }
8926         GameListDestroy();
8927         n = 1;
8928     }
8929     if (n == 0) n = 1;
8930     return LoadGame(f, n, title, FALSE);
8931 }
8932
8933
8934 void
8935 MakeRegisteredMove()
8936 {
8937     int fromX, fromY, toX, toY;
8938     char promoChar;
8939     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8940         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8941           case CMAIL_MOVE:
8942           case CMAIL_DRAW:
8943             if (appData.debugMode)
8944               fprintf(debugFP, "Restoring %s for game %d\n",
8945                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8946     
8947             thinkOutput[0] = NULLCHAR;
8948             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8949             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8950             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8951             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8952             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8953             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8954             MakeMove(fromX, fromY, toX, toY, promoChar);
8955             ShowMove(fromX, fromY, toX, toY);
8956               
8957             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8958                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8959               case MT_NONE:
8960               case MT_CHECK:
8961                 break;
8962                 
8963               case MT_CHECKMATE:
8964               case MT_STAINMATE:
8965                 if (WhiteOnMove(currentMove)) {
8966                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8967                 } else {
8968                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8969                 }
8970                 break;
8971                 
8972               case MT_STALEMATE:
8973                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8974                 break;
8975             }
8976
8977             break;
8978             
8979           case CMAIL_RESIGN:
8980             if (WhiteOnMove(currentMove)) {
8981                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8982             } else {
8983                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8984             }
8985             break;
8986             
8987           case CMAIL_ACCEPT:
8988             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8989             break;
8990               
8991           default:
8992             break;
8993         }
8994     }
8995
8996     return;
8997 }
8998
8999 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9000 int
9001 CmailLoadGame(f, gameNumber, title, useList)
9002      FILE *f;
9003      int gameNumber;
9004      char *title;
9005      int useList;
9006 {
9007     int retVal;
9008
9009     if (gameNumber > nCmailGames) {
9010         DisplayError(_("No more games in this message"), 0);
9011         return FALSE;
9012     }
9013     if (f == lastLoadGameFP) {
9014         int offset = gameNumber - lastLoadGameNumber;
9015         if (offset == 0) {
9016             cmailMsg[0] = NULLCHAR;
9017             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9018                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9019                 nCmailMovesRegistered--;
9020             }
9021             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9022             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9023                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9024             }
9025         } else {
9026             if (! RegisterMove()) return FALSE;
9027         }
9028     }
9029
9030     retVal = LoadGame(f, gameNumber, title, useList);
9031
9032     /* Make move registered during previous look at this game, if any */
9033     MakeRegisteredMove();
9034
9035     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9036         commentList[currentMove]
9037           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9038         DisplayComment(currentMove - 1, commentList[currentMove]);
9039     }
9040
9041     return retVal;
9042 }
9043
9044 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9045 int
9046 ReloadGame(offset)
9047      int offset;
9048 {
9049     int gameNumber = lastLoadGameNumber + offset;
9050     if (lastLoadGameFP == NULL) {
9051         DisplayError(_("No game has been loaded yet"), 0);
9052         return FALSE;
9053     }
9054     if (gameNumber <= 0) {
9055         DisplayError(_("Can't back up any further"), 0);
9056         return FALSE;
9057     }
9058     if (cmailMsgLoaded) {
9059         return CmailLoadGame(lastLoadGameFP, gameNumber,
9060                              lastLoadGameTitle, lastLoadGameUseList);
9061     } else {
9062         return LoadGame(lastLoadGameFP, gameNumber,
9063                         lastLoadGameTitle, lastLoadGameUseList);
9064     }
9065 }
9066
9067
9068
9069 /* Load the nth game from open file f */
9070 int
9071 LoadGame(f, gameNumber, title, useList)
9072      FILE *f;
9073      int gameNumber;
9074      char *title;
9075      int useList;
9076 {
9077     ChessMove cm;
9078     char buf[MSG_SIZ];
9079     int gn = gameNumber;
9080     ListGame *lg = NULL;
9081     int numPGNTags = 0;
9082     int err;
9083     GameMode oldGameMode;
9084     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9085
9086     if (appData.debugMode) 
9087         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9088
9089     if (gameMode == Training )
9090         SetTrainingModeOff();
9091
9092     oldGameMode = gameMode;
9093     if (gameMode != BeginningOfGame) {
9094       Reset(FALSE, TRUE);
9095     }
9096
9097     gameFileFP = f;
9098     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9099         fclose(lastLoadGameFP);
9100     }
9101
9102     if (useList) {
9103         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9104         
9105         if (lg) {
9106             fseek(f, lg->offset, 0);
9107             GameListHighlight(gameNumber);
9108             gn = 1;
9109         }
9110         else {
9111             DisplayError(_("Game number out of range"), 0);
9112             return FALSE;
9113         }
9114     } else {
9115         GameListDestroy();
9116         if (fseek(f, 0, 0) == -1) {
9117             if (f == lastLoadGameFP ?
9118                 gameNumber == lastLoadGameNumber + 1 :
9119                 gameNumber == 1) {
9120                 gn = 1;
9121             } else {
9122                 DisplayError(_("Can't seek on game file"), 0);
9123                 return FALSE;
9124             }
9125         }
9126     }
9127     lastLoadGameFP = f;
9128     lastLoadGameNumber = gameNumber;
9129     strcpy(lastLoadGameTitle, title);
9130     lastLoadGameUseList = useList;
9131
9132     yynewfile(f);
9133
9134     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9135       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9136                 lg->gameInfo.black);
9137             DisplayTitle(buf);
9138     } else if (*title != NULLCHAR) {
9139         if (gameNumber > 1) {
9140             sprintf(buf, "%s %d", title, gameNumber);
9141             DisplayTitle(buf);
9142         } else {
9143             DisplayTitle(title);
9144         }
9145     }
9146
9147     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9148         gameMode = PlayFromGameFile;
9149         ModeHighlight();
9150     }
9151
9152     currentMove = forwardMostMove = backwardMostMove = 0;
9153     CopyBoard(boards[0], initialPosition);
9154     StopClocks();
9155
9156     /*
9157      * Skip the first gn-1 games in the file.
9158      * Also skip over anything that precedes an identifiable 
9159      * start of game marker, to avoid being confused by 
9160      * garbage at the start of the file.  Currently 
9161      * recognized start of game markers are the move number "1",
9162      * the pattern "gnuchess .* game", the pattern
9163      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9164      * A game that starts with one of the latter two patterns
9165      * will also have a move number 1, possibly
9166      * following a position diagram.
9167      * 5-4-02: Let's try being more lenient and allowing a game to
9168      * start with an unnumbered move.  Does that break anything?
9169      */
9170     cm = lastLoadGameStart = (ChessMove) 0;
9171     while (gn > 0) {
9172         yyboardindex = forwardMostMove;
9173         cm = (ChessMove) yylex();
9174         switch (cm) {
9175           case (ChessMove) 0:
9176             if (cmailMsgLoaded) {
9177                 nCmailGames = CMAIL_MAX_GAMES - gn;
9178             } else {
9179                 Reset(TRUE, TRUE);
9180                 DisplayError(_("Game not found in file"), 0);
9181             }
9182             return FALSE;
9183
9184           case GNUChessGame:
9185           case XBoardGame:
9186             gn--;
9187             lastLoadGameStart = cm;
9188             break;
9189             
9190           case MoveNumberOne:
9191             switch (lastLoadGameStart) {
9192               case GNUChessGame:
9193               case XBoardGame:
9194               case PGNTag:
9195                 break;
9196               case MoveNumberOne:
9197               case (ChessMove) 0:
9198                 gn--;           /* count this game */
9199                 lastLoadGameStart = cm;
9200                 break;
9201               default:
9202                 /* impossible */
9203                 break;
9204             }
9205             break;
9206
9207           case PGNTag:
9208             switch (lastLoadGameStart) {
9209               case GNUChessGame:
9210               case PGNTag:
9211               case MoveNumberOne:
9212               case (ChessMove) 0:
9213                 gn--;           /* count this game */
9214                 lastLoadGameStart = cm;
9215                 break;
9216               case XBoardGame:
9217                 lastLoadGameStart = cm; /* game counted already */
9218                 break;
9219               default:
9220                 /* impossible */
9221                 break;
9222             }
9223             if (gn > 0) {
9224                 do {
9225                     yyboardindex = forwardMostMove;
9226                     cm = (ChessMove) yylex();
9227                 } while (cm == PGNTag || cm == Comment);
9228             }
9229             break;
9230
9231           case WhiteWins:
9232           case BlackWins:
9233           case GameIsDrawn:
9234             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9235                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9236                     != CMAIL_OLD_RESULT) {
9237                     nCmailResults ++ ;
9238                     cmailResult[  CMAIL_MAX_GAMES
9239                                 - gn - 1] = CMAIL_OLD_RESULT;
9240                 }
9241             }
9242             break;
9243
9244           case NormalMove:
9245             /* Only a NormalMove can be at the start of a game
9246              * without a position diagram. */
9247             if (lastLoadGameStart == (ChessMove) 0) {
9248               gn--;
9249               lastLoadGameStart = MoveNumberOne;
9250             }
9251             break;
9252
9253           default:
9254             break;
9255         }
9256     }
9257     
9258     if (appData.debugMode)
9259       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9260
9261     if (cm == XBoardGame) {
9262         /* Skip any header junk before position diagram and/or move 1 */
9263         for (;;) {
9264             yyboardindex = forwardMostMove;
9265             cm = (ChessMove) yylex();
9266
9267             if (cm == (ChessMove) 0 ||
9268                 cm == GNUChessGame || cm == XBoardGame) {
9269                 /* Empty game; pretend end-of-file and handle later */
9270                 cm = (ChessMove) 0;
9271                 break;
9272             }
9273
9274             if (cm == MoveNumberOne || cm == PositionDiagram ||
9275                 cm == PGNTag || cm == Comment)
9276               break;
9277         }
9278     } else if (cm == GNUChessGame) {
9279         if (gameInfo.event != NULL) {
9280             free(gameInfo.event);
9281         }
9282         gameInfo.event = StrSave(yy_text);
9283     }   
9284
9285     startedFromSetupPosition = FALSE;
9286     while (cm == PGNTag) {
9287         if (appData.debugMode) 
9288           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9289         err = ParsePGNTag(yy_text, &gameInfo);
9290         if (!err) numPGNTags++;
9291
9292         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9293         if(gameInfo.variant != oldVariant) {
9294             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9295             InitPosition(TRUE);
9296             oldVariant = gameInfo.variant;
9297             if (appData.debugMode) 
9298               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9299         }
9300
9301
9302         if (gameInfo.fen != NULL) {
9303           Board initial_position;
9304           startedFromSetupPosition = TRUE;
9305           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9306             Reset(TRUE, TRUE);
9307             DisplayError(_("Bad FEN position in file"), 0);
9308             return FALSE;
9309           }
9310           CopyBoard(boards[0], initial_position);
9311           if (blackPlaysFirst) {
9312             currentMove = forwardMostMove = backwardMostMove = 1;
9313             CopyBoard(boards[1], initial_position);
9314             strcpy(moveList[0], "");
9315             strcpy(parseList[0], "");
9316             timeRemaining[0][1] = whiteTimeRemaining;
9317             timeRemaining[1][1] = blackTimeRemaining;
9318             if (commentList[0] != NULL) {
9319               commentList[1] = commentList[0];
9320               commentList[0] = NULL;
9321             }
9322           } else {
9323             currentMove = forwardMostMove = backwardMostMove = 0;
9324           }
9325           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9326           {   int i;
9327               initialRulePlies = FENrulePlies;
9328               epStatus[forwardMostMove] = FENepStatus;
9329               for( i=0; i< nrCastlingRights; i++ )
9330                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9331           }
9332           yyboardindex = forwardMostMove;
9333           free(gameInfo.fen);
9334           gameInfo.fen = NULL;
9335         }
9336
9337         yyboardindex = forwardMostMove;
9338         cm = (ChessMove) yylex();
9339
9340         /* Handle comments interspersed among the tags */
9341         while (cm == Comment) {
9342             char *p;
9343             if (appData.debugMode) 
9344               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9345             p = yy_text;
9346             if (*p == '{' || *p == '[' || *p == '(') {
9347                 p[strlen(p) - 1] = NULLCHAR;
9348                 p++;
9349             }
9350             while (*p == '\n') p++;
9351             AppendComment(currentMove, p);
9352             yyboardindex = forwardMostMove;
9353             cm = (ChessMove) yylex();
9354         }
9355     }
9356
9357     /* don't rely on existence of Event tag since if game was
9358      * pasted from clipboard the Event tag may not exist
9359      */
9360     if (numPGNTags > 0){
9361         char *tags;
9362         if (gameInfo.variant == VariantNormal) {
9363           gameInfo.variant = StringToVariant(gameInfo.event);
9364         }
9365         if (!matchMode) {
9366           if( appData.autoDisplayTags ) {
9367             tags = PGNTags(&gameInfo);
9368             TagsPopUp(tags, CmailMsg());
9369             free(tags);
9370           }
9371         }
9372     } else {
9373         /* Make something up, but don't display it now */
9374         SetGameInfo();
9375         TagsPopDown();
9376     }
9377
9378     if (cm == PositionDiagram) {
9379         int i, j;
9380         char *p;
9381         Board initial_position;
9382
9383         if (appData.debugMode)
9384           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9385
9386         if (!startedFromSetupPosition) {
9387             p = yy_text;
9388             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9389               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9390                 switch (*p) {
9391                   case '[':
9392                   case '-':
9393                   case ' ':
9394                   case '\t':
9395                   case '\n':
9396                   case '\r':
9397                     break;
9398                   default:
9399                     initial_position[i][j++] = CharToPiece(*p);
9400                     break;
9401                 }
9402             while (*p == ' ' || *p == '\t' ||
9403                    *p == '\n' || *p == '\r') p++;
9404         
9405             if (strncmp(p, "black", strlen("black"))==0)
9406               blackPlaysFirst = TRUE;
9407             else
9408               blackPlaysFirst = FALSE;
9409             startedFromSetupPosition = TRUE;
9410         
9411             CopyBoard(boards[0], initial_position);
9412             if (blackPlaysFirst) {
9413                 currentMove = forwardMostMove = backwardMostMove = 1;
9414                 CopyBoard(boards[1], initial_position);
9415                 strcpy(moveList[0], "");
9416                 strcpy(parseList[0], "");
9417                 timeRemaining[0][1] = whiteTimeRemaining;
9418                 timeRemaining[1][1] = blackTimeRemaining;
9419                 if (commentList[0] != NULL) {
9420                     commentList[1] = commentList[0];
9421                     commentList[0] = NULL;
9422                 }
9423             } else {
9424                 currentMove = forwardMostMove = backwardMostMove = 0;
9425             }
9426         }
9427         yyboardindex = forwardMostMove;
9428         cm = (ChessMove) yylex();
9429     }
9430
9431     if (first.pr == NoProc) {
9432         StartChessProgram(&first);
9433     }
9434     InitChessProgram(&first, FALSE);
9435     SendToProgram("force\n", &first);
9436     if (startedFromSetupPosition) {
9437         SendBoard(&first, forwardMostMove);
9438     if (appData.debugMode) {
9439         fprintf(debugFP, "Load Game\n");
9440     }
9441         DisplayBothClocks();
9442     }      
9443
9444     /* [HGM] server: flag to write setup moves in broadcast file as one */
9445     loadFlag = appData.suppressLoadMoves;
9446
9447     while (cm == Comment) {
9448         char *p;
9449         if (appData.debugMode) 
9450           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9451         p = yy_text;
9452         if (*p == '{' || *p == '[' || *p == '(') {
9453             p[strlen(p) - 1] = NULLCHAR;
9454             p++;
9455         }
9456         while (*p == '\n') p++;
9457         AppendComment(currentMove, p);
9458         yyboardindex = forwardMostMove;
9459         cm = (ChessMove) yylex();
9460     }
9461
9462     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9463         cm == WhiteWins || cm == BlackWins ||
9464         cm == GameIsDrawn || cm == GameUnfinished) {
9465         DisplayMessage("", _("No moves in game"));
9466         if (cmailMsgLoaded) {
9467             if (appData.debugMode)
9468               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9469             ClearHighlights();
9470             flipView = FALSE;
9471         }
9472         DrawPosition(FALSE, boards[currentMove]);
9473         DisplayBothClocks();
9474         gameMode = EditGame;
9475         ModeHighlight();
9476         gameFileFP = NULL;
9477         cmailOldMove = 0;
9478         return TRUE;
9479     }
9480
9481     // [HGM] PV info: routine tests if comment empty
9482     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9483         DisplayComment(currentMove - 1, commentList[currentMove]);
9484     }
9485     if (!matchMode && appData.timeDelay != 0) 
9486       DrawPosition(FALSE, boards[currentMove]);
9487
9488     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9489       programStats.ok_to_send = 1;
9490     }
9491
9492     /* if the first token after the PGN tags is a move
9493      * and not move number 1, retrieve it from the parser 
9494      */
9495     if (cm != MoveNumberOne)
9496         LoadGameOneMove(cm);
9497
9498     /* load the remaining moves from the file */
9499     while (LoadGameOneMove((ChessMove)0)) {
9500       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9501       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9502     }
9503
9504     /* rewind to the start of the game */
9505     currentMove = backwardMostMove;
9506
9507     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9508
9509     if (oldGameMode == AnalyzeFile ||
9510         oldGameMode == AnalyzeMode) {
9511       AnalyzeFileEvent();
9512     }
9513
9514     if (matchMode || appData.timeDelay == 0) {
9515       ToEndEvent();
9516       gameMode = EditGame;
9517       ModeHighlight();
9518     } else if (appData.timeDelay > 0) {
9519       AutoPlayGameLoop();
9520     }
9521
9522     if (appData.debugMode) 
9523         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9524
9525     loadFlag = 0; /* [HGM] true game starts */
9526     return TRUE;
9527 }
9528
9529 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9530 int
9531 ReloadPosition(offset)
9532      int offset;
9533 {
9534     int positionNumber = lastLoadPositionNumber + offset;
9535     if (lastLoadPositionFP == NULL) {
9536         DisplayError(_("No position has been loaded yet"), 0);
9537         return FALSE;
9538     }
9539     if (positionNumber <= 0) {
9540         DisplayError(_("Can't back up any further"), 0);
9541         return FALSE;
9542     }
9543     return LoadPosition(lastLoadPositionFP, positionNumber,
9544                         lastLoadPositionTitle);
9545 }
9546
9547 /* Load the nth position from the given file */
9548 int
9549 LoadPositionFromFile(filename, n, title)
9550      char *filename;
9551      int n;
9552      char *title;
9553 {
9554     FILE *f;
9555     char buf[MSG_SIZ];
9556
9557     if (strcmp(filename, "-") == 0) {
9558         return LoadPosition(stdin, n, "stdin");
9559     } else {
9560         f = fopen(filename, "rb");
9561         if (f == NULL) {
9562             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9563             DisplayError(buf, errno);
9564             return FALSE;
9565         } else {
9566             return LoadPosition(f, n, title);
9567         }
9568     }
9569 }
9570
9571 /* Load the nth position from the given open file, and close it */
9572 int
9573 LoadPosition(f, positionNumber, title)
9574      FILE *f;
9575      int positionNumber;
9576      char *title;
9577 {
9578     char *p, line[MSG_SIZ];
9579     Board initial_position;
9580     int i, j, fenMode, pn;
9581     
9582     if (gameMode == Training )
9583         SetTrainingModeOff();
9584
9585     if (gameMode != BeginningOfGame) {
9586         Reset(FALSE, TRUE);
9587     }
9588     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9589         fclose(lastLoadPositionFP);
9590     }
9591     if (positionNumber == 0) positionNumber = 1;
9592     lastLoadPositionFP = f;
9593     lastLoadPositionNumber = positionNumber;
9594     strcpy(lastLoadPositionTitle, title);
9595     if (first.pr == NoProc) {
9596       StartChessProgram(&first);
9597       InitChessProgram(&first, FALSE);
9598     }    
9599     pn = positionNumber;
9600     if (positionNumber < 0) {
9601         /* Negative position number means to seek to that byte offset */
9602         if (fseek(f, -positionNumber, 0) == -1) {
9603             DisplayError(_("Can't seek on position file"), 0);
9604             return FALSE;
9605         };
9606         pn = 1;
9607     } else {
9608         if (fseek(f, 0, 0) == -1) {
9609             if (f == lastLoadPositionFP ?
9610                 positionNumber == lastLoadPositionNumber + 1 :
9611                 positionNumber == 1) {
9612                 pn = 1;
9613             } else {
9614                 DisplayError(_("Can't seek on position file"), 0);
9615                 return FALSE;
9616             }
9617         }
9618     }
9619     /* See if this file is FEN or old-style xboard */
9620     if (fgets(line, MSG_SIZ, f) == NULL) {
9621         DisplayError(_("Position not found in file"), 0);
9622         return FALSE;
9623     }
9624     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9625     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9626
9627     if (pn >= 2) {
9628         if (fenMode || line[0] == '#') pn--;
9629         while (pn > 0) {
9630             /* skip positions before number pn */
9631             if (fgets(line, MSG_SIZ, f) == NULL) {
9632                 Reset(TRUE, TRUE);
9633                 DisplayError(_("Position not found in file"), 0);
9634                 return FALSE;
9635             }
9636             if (fenMode || line[0] == '#') pn--;
9637         }
9638     }
9639
9640     if (fenMode) {
9641         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9642             DisplayError(_("Bad FEN position in file"), 0);
9643             return FALSE;
9644         }
9645     } else {
9646         (void) fgets(line, MSG_SIZ, f);
9647         (void) fgets(line, MSG_SIZ, f);
9648     
9649         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9650             (void) fgets(line, MSG_SIZ, f);
9651             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9652                 if (*p == ' ')
9653                   continue;
9654                 initial_position[i][j++] = CharToPiece(*p);
9655             }
9656         }
9657     
9658         blackPlaysFirst = FALSE;
9659         if (!feof(f)) {
9660             (void) fgets(line, MSG_SIZ, f);
9661             if (strncmp(line, "black", strlen("black"))==0)
9662               blackPlaysFirst = TRUE;
9663         }
9664     }
9665     startedFromSetupPosition = TRUE;
9666     
9667     SendToProgram("force\n", &first);
9668     CopyBoard(boards[0], initial_position);
9669     if (blackPlaysFirst) {
9670         currentMove = forwardMostMove = backwardMostMove = 1;
9671         strcpy(moveList[0], "");
9672         strcpy(parseList[0], "");
9673         CopyBoard(boards[1], initial_position);
9674         DisplayMessage("", _("Black to play"));
9675     } else {
9676         currentMove = forwardMostMove = backwardMostMove = 0;
9677         DisplayMessage("", _("White to play"));
9678     }
9679           /* [HGM] copy FEN attributes as well */
9680           {   int i;
9681               initialRulePlies = FENrulePlies;
9682               epStatus[forwardMostMove] = FENepStatus;
9683               for( i=0; i< nrCastlingRights; i++ )
9684                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9685           }
9686     SendBoard(&first, forwardMostMove);
9687     if (appData.debugMode) {
9688 int i, j;
9689   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9690   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9691         fprintf(debugFP, "Load Position\n");
9692     }
9693
9694     if (positionNumber > 1) {
9695         sprintf(line, "%s %d", title, positionNumber);
9696         DisplayTitle(line);
9697     } else {
9698         DisplayTitle(title);
9699     }
9700     gameMode = EditGame;
9701     ModeHighlight();
9702     ResetClocks();
9703     timeRemaining[0][1] = whiteTimeRemaining;
9704     timeRemaining[1][1] = blackTimeRemaining;
9705     DrawPosition(FALSE, boards[currentMove]);
9706    
9707     return TRUE;
9708 }
9709
9710
9711 void
9712 CopyPlayerNameIntoFileName(dest, src)
9713      char **dest, *src;
9714 {
9715     while (*src != NULLCHAR && *src != ',') {
9716         if (*src == ' ') {
9717             *(*dest)++ = '_';
9718             src++;
9719         } else {
9720             *(*dest)++ = *src++;
9721         }
9722     }
9723 }
9724
9725 char *DefaultFileName(ext)
9726      char *ext;
9727 {
9728     static char def[MSG_SIZ];
9729     char *p;
9730
9731     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9732         p = def;
9733         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9734         *p++ = '-';
9735         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9736         *p++ = '.';
9737         strcpy(p, ext);
9738     } else {
9739         def[0] = NULLCHAR;
9740     }
9741     return def;
9742 }
9743
9744 /* Save the current game to the given file */
9745 int
9746 SaveGameToFile(filename, append)
9747      char *filename;
9748      int append;
9749 {
9750     FILE *f;
9751     char buf[MSG_SIZ];
9752
9753     if (strcmp(filename, "-") == 0) {
9754         return SaveGame(stdout, 0, NULL);
9755     } else {
9756         f = fopen(filename, append ? "a" : "w");
9757         if (f == NULL) {
9758             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9759             DisplayError(buf, errno);
9760             return FALSE;
9761         } else {
9762             return SaveGame(f, 0, NULL);
9763         }
9764     }
9765 }
9766
9767 char *
9768 SavePart(str)
9769      char *str;
9770 {
9771     static char buf[MSG_SIZ];
9772     char *p;
9773     
9774     p = strchr(str, ' ');
9775     if (p == NULL) return str;
9776     strncpy(buf, str, p - str);
9777     buf[p - str] = NULLCHAR;
9778     return buf;
9779 }
9780
9781 #define PGN_MAX_LINE 75
9782
9783 #define PGN_SIDE_WHITE  0
9784 #define PGN_SIDE_BLACK  1
9785
9786 /* [AS] */
9787 static int FindFirstMoveOutOfBook( int side )
9788 {
9789     int result = -1;
9790
9791     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9792         int index = backwardMostMove;
9793         int has_book_hit = 0;
9794
9795         if( (index % 2) != side ) {
9796             index++;
9797         }
9798
9799         while( index < forwardMostMove ) {
9800             /* Check to see if engine is in book */
9801             int depth = pvInfoList[index].depth;
9802             int score = pvInfoList[index].score;
9803             int in_book = 0;
9804
9805             if( depth <= 2 ) {
9806                 in_book = 1;
9807             }
9808             else if( score == 0 && depth == 63 ) {
9809                 in_book = 1; /* Zappa */
9810             }
9811             else if( score == 2 && depth == 99 ) {
9812                 in_book = 1; /* Abrok */
9813             }
9814
9815             has_book_hit += in_book;
9816
9817             if( ! in_book ) {
9818                 result = index;
9819
9820                 break;
9821             }
9822
9823             index += 2;
9824         }
9825     }
9826
9827     return result;
9828 }
9829
9830 /* [AS] */
9831 void GetOutOfBookInfo( char * buf )
9832 {
9833     int oob[2];
9834     int i;
9835     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9836
9837     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9838     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9839
9840     *buf = '\0';
9841
9842     if( oob[0] >= 0 || oob[1] >= 0 ) {
9843         for( i=0; i<2; i++ ) {
9844             int idx = oob[i];
9845
9846             if( idx >= 0 ) {
9847                 if( i > 0 && oob[0] >= 0 ) {
9848                     strcat( buf, "   " );
9849                 }
9850
9851                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9852                 sprintf( buf+strlen(buf), "%s%.2f", 
9853                     pvInfoList[idx].score >= 0 ? "+" : "",
9854                     pvInfoList[idx].score / 100.0 );
9855             }
9856         }
9857     }
9858 }
9859
9860 /* Save game in PGN style and close the file */
9861 int
9862 SaveGamePGN(f)
9863      FILE *f;
9864 {
9865     int i, offset, linelen, newblock;
9866     time_t tm;
9867 //    char *movetext;
9868     char numtext[32];
9869     int movelen, numlen, blank;
9870     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9871
9872     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9873     
9874     tm = time((time_t *) NULL);
9875     
9876     PrintPGNTags(f, &gameInfo);
9877     
9878     if (backwardMostMove > 0 || startedFromSetupPosition) {
9879         char *fen = PositionToFEN(backwardMostMove, NULL);
9880         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9881         fprintf(f, "\n{--------------\n");
9882         PrintPosition(f, backwardMostMove);
9883         fprintf(f, "--------------}\n");
9884         free(fen);
9885     }
9886     else {
9887         /* [AS] Out of book annotation */
9888         if( appData.saveOutOfBookInfo ) {
9889             char buf[64];
9890
9891             GetOutOfBookInfo( buf );
9892
9893             if( buf[0] != '\0' ) {
9894                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9895             }
9896         }
9897
9898         fprintf(f, "\n");
9899     }
9900
9901     i = backwardMostMove;
9902     linelen = 0;
9903     newblock = TRUE;
9904
9905     while (i < forwardMostMove) {
9906         /* Print comments preceding this move */
9907         if (commentList[i] != NULL) {
9908             if (linelen > 0) fprintf(f, "\n");
9909             fprintf(f, "{\n%s}\n", commentList[i]);
9910             linelen = 0;
9911             newblock = TRUE;
9912         }
9913
9914         /* Format move number */
9915         if ((i % 2) == 0) {
9916             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9917         } else {
9918             if (newblock) {
9919                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9920             } else {
9921                 numtext[0] = NULLCHAR;
9922             }
9923         }
9924         numlen = strlen(numtext);
9925         newblock = FALSE;
9926
9927         /* Print move number */
9928         blank = linelen > 0 && numlen > 0;
9929         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9930             fprintf(f, "\n");
9931             linelen = 0;
9932             blank = 0;
9933         }
9934         if (blank) {
9935             fprintf(f, " ");
9936             linelen++;
9937         }
9938         fprintf(f, "%s", numtext);
9939         linelen += numlen;
9940
9941         /* Get move */
9942         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9943         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9944
9945         /* Print move */
9946         blank = linelen > 0 && movelen > 0;
9947         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9948             fprintf(f, "\n");
9949             linelen = 0;
9950             blank = 0;
9951         }
9952         if (blank) {
9953             fprintf(f, " ");
9954             linelen++;
9955         }
9956         fprintf(f, "%s", move_buffer);
9957         linelen += movelen;
9958
9959         /* [AS] Add PV info if present */
9960         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9961             /* [HGM] add time */
9962             char buf[MSG_SIZ]; int seconds;
9963
9964             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9965
9966             if( seconds <= 0) buf[0] = 0; else
9967             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9968                 seconds = (seconds + 4)/10; // round to full seconds
9969                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9970                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9971             }
9972
9973             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9974                 pvInfoList[i].score >= 0 ? "+" : "",
9975                 pvInfoList[i].score / 100.0,
9976                 pvInfoList[i].depth,
9977                 buf );
9978
9979             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9980
9981             /* Print score/depth */
9982             blank = linelen > 0 && movelen > 0;
9983             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9984                 fprintf(f, "\n");
9985                 linelen = 0;
9986                 blank = 0;
9987             }
9988             if (blank) {
9989                 fprintf(f, " ");
9990                 linelen++;
9991             }
9992             fprintf(f, "%s", move_buffer);
9993             linelen += movelen;
9994         }
9995
9996         i++;
9997     }
9998     
9999     /* Start a new line */
10000     if (linelen > 0) fprintf(f, "\n");
10001
10002     /* Print comments after last move */
10003     if (commentList[i] != NULL) {
10004         fprintf(f, "{\n%s}\n", commentList[i]);
10005     }
10006
10007     /* Print result */
10008     if (gameInfo.resultDetails != NULL &&
10009         gameInfo.resultDetails[0] != NULLCHAR) {
10010         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10011                 PGNResult(gameInfo.result));
10012     } else {
10013         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10014     }
10015
10016     fclose(f);
10017     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10018     return TRUE;
10019 }
10020
10021 /* Save game in old style and close the file */
10022 int
10023 SaveGameOldStyle(f)
10024      FILE *f;
10025 {
10026     int i, offset;
10027     time_t tm;
10028     
10029     tm = time((time_t *) NULL);
10030     
10031     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10032     PrintOpponents(f);
10033     
10034     if (backwardMostMove > 0 || startedFromSetupPosition) {
10035         fprintf(f, "\n[--------------\n");
10036         PrintPosition(f, backwardMostMove);
10037         fprintf(f, "--------------]\n");
10038     } else {
10039         fprintf(f, "\n");
10040     }
10041
10042     i = backwardMostMove;
10043     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10044
10045     while (i < forwardMostMove) {
10046         if (commentList[i] != NULL) {
10047             fprintf(f, "[%s]\n", commentList[i]);
10048         }
10049
10050         if ((i % 2) == 1) {
10051             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10052             i++;
10053         } else {
10054             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10055             i++;
10056             if (commentList[i] != NULL) {
10057                 fprintf(f, "\n");
10058                 continue;
10059             }
10060             if (i >= forwardMostMove) {
10061                 fprintf(f, "\n");
10062                 break;
10063             }
10064             fprintf(f, "%s\n", parseList[i]);
10065             i++;
10066         }
10067     }
10068     
10069     if (commentList[i] != NULL) {
10070         fprintf(f, "[%s]\n", commentList[i]);
10071     }
10072
10073     /* This isn't really the old style, but it's close enough */
10074     if (gameInfo.resultDetails != NULL &&
10075         gameInfo.resultDetails[0] != NULLCHAR) {
10076         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10077                 gameInfo.resultDetails);
10078     } else {
10079         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10080     }
10081
10082     fclose(f);
10083     return TRUE;
10084 }
10085
10086 /* Save the current game to open file f and close the file */
10087 int
10088 SaveGame(f, dummy, dummy2)
10089      FILE *f;
10090      int dummy;
10091      char *dummy2;
10092 {
10093     if (gameMode == EditPosition) EditPositionDone(TRUE);
10094     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10095     if (appData.oldSaveStyle)
10096       return SaveGameOldStyle(f);
10097     else
10098       return SaveGamePGN(f);
10099 }
10100
10101 /* Save the current position to the given file */
10102 int
10103 SavePositionToFile(filename)
10104      char *filename;
10105 {
10106     FILE *f;
10107     char buf[MSG_SIZ];
10108
10109     if (strcmp(filename, "-") == 0) {
10110         return SavePosition(stdout, 0, NULL);
10111     } else {
10112         f = fopen(filename, "a");
10113         if (f == NULL) {
10114             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10115             DisplayError(buf, errno);
10116             return FALSE;
10117         } else {
10118             SavePosition(f, 0, NULL);
10119             return TRUE;
10120         }
10121     }
10122 }
10123
10124 /* Save the current position to the given open file and close the file */
10125 int
10126 SavePosition(f, dummy, dummy2)
10127      FILE *f;
10128      int dummy;
10129      char *dummy2;
10130 {
10131     time_t tm;
10132     char *fen;
10133
10134     if (gameMode == EditPosition) EditPositionDone(TRUE);
10135     if (appData.oldSaveStyle) {
10136         tm = time((time_t *) NULL);
10137     
10138         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10139         PrintOpponents(f);
10140         fprintf(f, "[--------------\n");
10141         PrintPosition(f, currentMove);
10142         fprintf(f, "--------------]\n");
10143     } else {
10144         fen = PositionToFEN(currentMove, NULL);
10145         fprintf(f, "%s\n", fen);
10146         free(fen);
10147     }
10148     fclose(f);
10149     return TRUE;
10150 }
10151
10152 void
10153 ReloadCmailMsgEvent(unregister)
10154      int unregister;
10155 {
10156 #if !WIN32
10157     static char *inFilename = NULL;
10158     static char *outFilename;
10159     int i;
10160     struct stat inbuf, outbuf;
10161     int status;
10162     
10163     /* Any registered moves are unregistered if unregister is set, */
10164     /* i.e. invoked by the signal handler */
10165     if (unregister) {
10166         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10167             cmailMoveRegistered[i] = FALSE;
10168             if (cmailCommentList[i] != NULL) {
10169                 free(cmailCommentList[i]);
10170                 cmailCommentList[i] = NULL;
10171             }
10172         }
10173         nCmailMovesRegistered = 0;
10174     }
10175
10176     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10177         cmailResult[i] = CMAIL_NOT_RESULT;
10178     }
10179     nCmailResults = 0;
10180
10181     if (inFilename == NULL) {
10182         /* Because the filenames are static they only get malloced once  */
10183         /* and they never get freed                                      */
10184         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10185         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10186
10187         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10188         sprintf(outFilename, "%s.out", appData.cmailGameName);
10189     }
10190     
10191     status = stat(outFilename, &outbuf);
10192     if (status < 0) {
10193         cmailMailedMove = FALSE;
10194     } else {
10195         status = stat(inFilename, &inbuf);
10196         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10197     }
10198     
10199     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10200        counts the games, notes how each one terminated, etc.
10201        
10202        It would be nice to remove this kludge and instead gather all
10203        the information while building the game list.  (And to keep it
10204        in the game list nodes instead of having a bunch of fixed-size
10205        parallel arrays.)  Note this will require getting each game's
10206        termination from the PGN tags, as the game list builder does
10207        not process the game moves.  --mann
10208        */
10209     cmailMsgLoaded = TRUE;
10210     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10211     
10212     /* Load first game in the file or popup game menu */
10213     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10214
10215 #endif /* !WIN32 */
10216     return;
10217 }
10218
10219 int
10220 RegisterMove()
10221 {
10222     FILE *f;
10223     char string[MSG_SIZ];
10224
10225     if (   cmailMailedMove
10226         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10227         return TRUE;            /* Allow free viewing  */
10228     }
10229
10230     /* Unregister move to ensure that we don't leave RegisterMove        */
10231     /* with the move registered when the conditions for registering no   */
10232     /* longer hold                                                       */
10233     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10234         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10235         nCmailMovesRegistered --;
10236
10237         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10238           {
10239               free(cmailCommentList[lastLoadGameNumber - 1]);
10240               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10241           }
10242     }
10243
10244     if (cmailOldMove == -1) {
10245         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10246         return FALSE;
10247     }
10248
10249     if (currentMove > cmailOldMove + 1) {
10250         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10251         return FALSE;
10252     }
10253
10254     if (currentMove < cmailOldMove) {
10255         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10256         return FALSE;
10257     }
10258
10259     if (forwardMostMove > currentMove) {
10260         /* Silently truncate extra moves */
10261         TruncateGame();
10262     }
10263
10264     if (   (currentMove == cmailOldMove + 1)
10265         || (   (currentMove == cmailOldMove)
10266             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10267                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10268         if (gameInfo.result != GameUnfinished) {
10269             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10270         }
10271
10272         if (commentList[currentMove] != NULL) {
10273             cmailCommentList[lastLoadGameNumber - 1]
10274               = StrSave(commentList[currentMove]);
10275         }
10276         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10277
10278         if (appData.debugMode)
10279           fprintf(debugFP, "Saving %s for game %d\n",
10280                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10281
10282         sprintf(string,
10283                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10284         
10285         f = fopen(string, "w");
10286         if (appData.oldSaveStyle) {
10287             SaveGameOldStyle(f); /* also closes the file */
10288             
10289             sprintf(string, "%s.pos.out", appData.cmailGameName);
10290             f = fopen(string, "w");
10291             SavePosition(f, 0, NULL); /* also closes the file */
10292         } else {
10293             fprintf(f, "{--------------\n");
10294             PrintPosition(f, currentMove);
10295             fprintf(f, "--------------}\n\n");
10296             
10297             SaveGame(f, 0, NULL); /* also closes the file*/
10298         }
10299         
10300         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10301         nCmailMovesRegistered ++;
10302     } else if (nCmailGames == 1) {
10303         DisplayError(_("You have not made a move yet"), 0);
10304         return FALSE;
10305     }
10306
10307     return TRUE;
10308 }
10309
10310 void
10311 MailMoveEvent()
10312 {
10313 #if !WIN32
10314     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10315     FILE *commandOutput;
10316     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10317     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10318     int nBuffers;
10319     int i;
10320     int archived;
10321     char *arcDir;
10322
10323     if (! cmailMsgLoaded) {
10324         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10325         return;
10326     }
10327
10328     if (nCmailGames == nCmailResults) {
10329         DisplayError(_("No unfinished games"), 0);
10330         return;
10331     }
10332
10333 #if CMAIL_PROHIBIT_REMAIL
10334     if (cmailMailedMove) {
10335         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);
10336         DisplayError(msg, 0);
10337         return;
10338     }
10339 #endif
10340
10341     if (! (cmailMailedMove || RegisterMove())) return;
10342     
10343     if (   cmailMailedMove
10344         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10345         sprintf(string, partCommandString,
10346                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10347         commandOutput = popen(string, "r");
10348
10349         if (commandOutput == NULL) {
10350             DisplayError(_("Failed to invoke cmail"), 0);
10351         } else {
10352             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10353                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10354             }
10355             if (nBuffers > 1) {
10356                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10357                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10358                 nBytes = MSG_SIZ - 1;
10359             } else {
10360                 (void) memcpy(msg, buffer, nBytes);
10361             }
10362             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10363
10364             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10365                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10366
10367                 archived = TRUE;
10368                 for (i = 0; i < nCmailGames; i ++) {
10369                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10370                         archived = FALSE;
10371                     }
10372                 }
10373                 if (   archived
10374                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10375                         != NULL)) {
10376                     sprintf(buffer, "%s/%s.%s.archive",
10377                             arcDir,
10378                             appData.cmailGameName,
10379                             gameInfo.date);
10380                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10381                     cmailMsgLoaded = FALSE;
10382                 }
10383             }
10384
10385             DisplayInformation(msg);
10386             pclose(commandOutput);
10387         }
10388     } else {
10389         if ((*cmailMsg) != '\0') {
10390             DisplayInformation(cmailMsg);
10391         }
10392     }
10393
10394     return;
10395 #endif /* !WIN32 */
10396 }
10397
10398 char *
10399 CmailMsg()
10400 {
10401 #if WIN32
10402     return NULL;
10403 #else
10404     int  prependComma = 0;
10405     char number[5];
10406     char string[MSG_SIZ];       /* Space for game-list */
10407     int  i;
10408     
10409     if (!cmailMsgLoaded) return "";
10410
10411     if (cmailMailedMove) {
10412         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10413     } else {
10414         /* Create a list of games left */
10415         sprintf(string, "[");
10416         for (i = 0; i < nCmailGames; i ++) {
10417             if (! (   cmailMoveRegistered[i]
10418                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10419                 if (prependComma) {
10420                     sprintf(number, ",%d", i + 1);
10421                 } else {
10422                     sprintf(number, "%d", i + 1);
10423                     prependComma = 1;
10424                 }
10425                 
10426                 strcat(string, number);
10427             }
10428         }
10429         strcat(string, "]");
10430
10431         if (nCmailMovesRegistered + nCmailResults == 0) {
10432             switch (nCmailGames) {
10433               case 1:
10434                 sprintf(cmailMsg,
10435                         _("Still need to make move for game\n"));
10436                 break;
10437                 
10438               case 2:
10439                 sprintf(cmailMsg,
10440                         _("Still need to make moves for both games\n"));
10441                 break;
10442                 
10443               default:
10444                 sprintf(cmailMsg,
10445                         _("Still need to make moves for all %d games\n"),
10446                         nCmailGames);
10447                 break;
10448             }
10449         } else {
10450             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10451               case 1:
10452                 sprintf(cmailMsg,
10453                         _("Still need to make a move for game %s\n"),
10454                         string);
10455                 break;
10456                 
10457               case 0:
10458                 if (nCmailResults == nCmailGames) {
10459                     sprintf(cmailMsg, _("No unfinished games\n"));
10460                 } else {
10461                     sprintf(cmailMsg, _("Ready to send mail\n"));
10462                 }
10463                 break;
10464                 
10465               default:
10466                 sprintf(cmailMsg,
10467                         _("Still need to make moves for games %s\n"),
10468                         string);
10469             }
10470         }
10471     }
10472     return cmailMsg;
10473 #endif /* WIN32 */
10474 }
10475
10476 void
10477 ResetGameEvent()
10478 {
10479     if (gameMode == Training)
10480       SetTrainingModeOff();
10481
10482     Reset(TRUE, TRUE);
10483     cmailMsgLoaded = FALSE;
10484     if (appData.icsActive) {
10485       SendToICS(ics_prefix);
10486       SendToICS("refresh\n");
10487     }
10488 }
10489
10490 void
10491 ExitEvent(status)
10492      int status;
10493 {
10494     exiting++;
10495     if (exiting > 2) {
10496       /* Give up on clean exit */
10497       exit(status);
10498     }
10499     if (exiting > 1) {
10500       /* Keep trying for clean exit */
10501       return;
10502     }
10503
10504     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10505
10506     if (telnetISR != NULL) {
10507       RemoveInputSource(telnetISR);
10508     }
10509     if (icsPR != NoProc) {
10510       DestroyChildProcess(icsPR, TRUE);
10511     }
10512
10513     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10514     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10515
10516     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10517     /* make sure this other one finishes before killing it!                  */
10518     if(endingGame) { int count = 0;
10519         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10520         while(endingGame && count++ < 10) DoSleep(1);
10521         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10522     }
10523
10524     /* Kill off chess programs */
10525     if (first.pr != NoProc) {
10526         ExitAnalyzeMode();
10527         
10528         DoSleep( appData.delayBeforeQuit );
10529         SendToProgram("quit\n", &first);
10530         DoSleep( appData.delayAfterQuit );
10531         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10532     }
10533     if (second.pr != NoProc) {
10534         DoSleep( appData.delayBeforeQuit );
10535         SendToProgram("quit\n", &second);
10536         DoSleep( appData.delayAfterQuit );
10537         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10538     }
10539     if (first.isr != NULL) {
10540         RemoveInputSource(first.isr);
10541     }
10542     if (second.isr != NULL) {
10543         RemoveInputSource(second.isr);
10544     }
10545
10546     ShutDownFrontEnd();
10547     exit(status);
10548 }
10549
10550 void
10551 PauseEvent()
10552 {
10553     if (appData.debugMode)
10554         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10555     if (pausing) {
10556         pausing = FALSE;
10557         ModeHighlight();
10558         if (gameMode == MachinePlaysWhite ||
10559             gameMode == MachinePlaysBlack) {
10560             StartClocks();
10561         } else {
10562             DisplayBothClocks();
10563         }
10564         if (gameMode == PlayFromGameFile) {
10565             if (appData.timeDelay >= 0) 
10566                 AutoPlayGameLoop();
10567         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10568             Reset(FALSE, TRUE);
10569             SendToICS(ics_prefix);
10570             SendToICS("refresh\n");
10571         } else if (currentMove < forwardMostMove) {
10572             ForwardInner(forwardMostMove);
10573         }
10574         pauseExamInvalid = FALSE;
10575     } else {
10576         switch (gameMode) {
10577           default:
10578             return;
10579           case IcsExamining:
10580             pauseExamForwardMostMove = forwardMostMove;
10581             pauseExamInvalid = FALSE;
10582             /* fall through */
10583           case IcsObserving:
10584           case IcsPlayingWhite:
10585           case IcsPlayingBlack:
10586             pausing = TRUE;
10587             ModeHighlight();
10588             return;
10589           case PlayFromGameFile:
10590             (void) StopLoadGameTimer();
10591             pausing = TRUE;
10592             ModeHighlight();
10593             break;
10594           case BeginningOfGame:
10595             if (appData.icsActive) return;
10596             /* else fall through */
10597           case MachinePlaysWhite:
10598           case MachinePlaysBlack:
10599           case TwoMachinesPlay:
10600             if (forwardMostMove == 0)
10601               return;           /* don't pause if no one has moved */
10602             if ((gameMode == MachinePlaysWhite &&
10603                  !WhiteOnMove(forwardMostMove)) ||
10604                 (gameMode == MachinePlaysBlack &&
10605                  WhiteOnMove(forwardMostMove))) {
10606                 StopClocks();
10607             }
10608             pausing = TRUE;
10609             ModeHighlight();
10610             break;
10611         }
10612     }
10613 }
10614
10615 void
10616 EditCommentEvent()
10617 {
10618     char title[MSG_SIZ];
10619
10620     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10621         strcpy(title, _("Edit comment"));
10622     } else {
10623         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10624                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10625                 parseList[currentMove - 1]);
10626     }
10627
10628     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10629 }
10630
10631
10632 void
10633 EditTagsEvent()
10634 {
10635     char *tags = PGNTags(&gameInfo);
10636     EditTagsPopUp(tags);
10637     free(tags);
10638 }
10639
10640 void
10641 AnalyzeModeEvent()
10642 {
10643     if (appData.noChessProgram || gameMode == AnalyzeMode)
10644       return;
10645
10646     if (gameMode != AnalyzeFile) {
10647         if (!appData.icsEngineAnalyze) {
10648                EditGameEvent();
10649                if (gameMode != EditGame) return;
10650         }
10651         ResurrectChessProgram();
10652         SendToProgram("analyze\n", &first);
10653         first.analyzing = TRUE;
10654         /*first.maybeThinking = TRUE;*/
10655         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10656         EngineOutputPopUp();
10657     }
10658     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10659     pausing = FALSE;
10660     ModeHighlight();
10661     SetGameInfo();
10662
10663     StartAnalysisClock();
10664     GetTimeMark(&lastNodeCountTime);
10665     lastNodeCount = 0;
10666 }
10667
10668 void
10669 AnalyzeFileEvent()
10670 {
10671     if (appData.noChessProgram || gameMode == AnalyzeFile)
10672       return;
10673
10674     if (gameMode != AnalyzeMode) {
10675         EditGameEvent();
10676         if (gameMode != EditGame) return;
10677         ResurrectChessProgram();
10678         SendToProgram("analyze\n", &first);
10679         first.analyzing = TRUE;
10680         /*first.maybeThinking = TRUE;*/
10681         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10682         EngineOutputPopUp();
10683     }
10684     gameMode = AnalyzeFile;
10685     pausing = FALSE;
10686     ModeHighlight();
10687     SetGameInfo();
10688
10689     StartAnalysisClock();
10690     GetTimeMark(&lastNodeCountTime);
10691     lastNodeCount = 0;
10692 }
10693
10694 void
10695 MachineWhiteEvent()
10696 {
10697     char buf[MSG_SIZ];
10698     char *bookHit = NULL;
10699
10700     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10701       return;
10702
10703
10704     if (gameMode == PlayFromGameFile || 
10705         gameMode == TwoMachinesPlay  || 
10706         gameMode == Training         || 
10707         gameMode == AnalyzeMode      || 
10708         gameMode == EndOfGame)
10709         EditGameEvent();
10710
10711     if (gameMode == EditPosition) 
10712         EditPositionDone(TRUE);
10713
10714     if (!WhiteOnMove(currentMove)) {
10715         DisplayError(_("It is not White's turn"), 0);
10716         return;
10717     }
10718   
10719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10720       ExitAnalyzeMode();
10721
10722     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10723         gameMode == AnalyzeFile)
10724         TruncateGame();
10725
10726     ResurrectChessProgram();    /* in case it isn't running */
10727     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10728         gameMode = MachinePlaysWhite;
10729         ResetClocks();
10730     } else
10731     gameMode = MachinePlaysWhite;
10732     pausing = FALSE;
10733     ModeHighlight();
10734     SetGameInfo();
10735     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10736     DisplayTitle(buf);
10737     if (first.sendName) {
10738       sprintf(buf, "name %s\n", gameInfo.black);
10739       SendToProgram(buf, &first);
10740     }
10741     if (first.sendTime) {
10742       if (first.useColors) {
10743         SendToProgram("black\n", &first); /*gnu kludge*/
10744       }
10745       SendTimeRemaining(&first, TRUE);
10746     }
10747     if (first.useColors) {
10748       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10749     }
10750     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10751     SetMachineThinkingEnables();
10752     first.maybeThinking = TRUE;
10753     StartClocks();
10754     firstMove = FALSE;
10755
10756     if (appData.autoFlipView && !flipView) {
10757       flipView = !flipView;
10758       DrawPosition(FALSE, NULL);
10759       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10760     }
10761
10762     if(bookHit) { // [HGM] book: simulate book reply
10763         static char bookMove[MSG_SIZ]; // a bit generous?
10764
10765         programStats.nodes = programStats.depth = programStats.time = 
10766         programStats.score = programStats.got_only_move = 0;
10767         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10768
10769         strcpy(bookMove, "move ");
10770         strcat(bookMove, bookHit);
10771         HandleMachineMove(bookMove, &first);
10772     }
10773 }
10774
10775 void
10776 MachineBlackEvent()
10777 {
10778     char buf[MSG_SIZ];
10779    char *bookHit = NULL;
10780
10781     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10782         return;
10783
10784
10785     if (gameMode == PlayFromGameFile || 
10786         gameMode == TwoMachinesPlay  || 
10787         gameMode == Training         || 
10788         gameMode == AnalyzeMode      || 
10789         gameMode == EndOfGame)
10790         EditGameEvent();
10791
10792     if (gameMode == EditPosition) 
10793         EditPositionDone(TRUE);
10794
10795     if (WhiteOnMove(currentMove)) {
10796         DisplayError(_("It is not Black's turn"), 0);
10797         return;
10798     }
10799     
10800     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10801       ExitAnalyzeMode();
10802
10803     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10804         gameMode == AnalyzeFile)
10805         TruncateGame();
10806
10807     ResurrectChessProgram();    /* in case it isn't running */
10808     gameMode = MachinePlaysBlack;
10809     pausing = FALSE;
10810     ModeHighlight();
10811     SetGameInfo();
10812     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10813     DisplayTitle(buf);
10814     if (first.sendName) {
10815       sprintf(buf, "name %s\n", gameInfo.white);
10816       SendToProgram(buf, &first);
10817     }
10818     if (first.sendTime) {
10819       if (first.useColors) {
10820         SendToProgram("white\n", &first); /*gnu kludge*/
10821       }
10822       SendTimeRemaining(&first, FALSE);
10823     }
10824     if (first.useColors) {
10825       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10826     }
10827     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10828     SetMachineThinkingEnables();
10829     first.maybeThinking = TRUE;
10830     StartClocks();
10831
10832     if (appData.autoFlipView && flipView) {
10833       flipView = !flipView;
10834       DrawPosition(FALSE, NULL);
10835       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10836     }
10837     if(bookHit) { // [HGM] book: simulate book reply
10838         static char bookMove[MSG_SIZ]; // a bit generous?
10839
10840         programStats.nodes = programStats.depth = programStats.time = 
10841         programStats.score = programStats.got_only_move = 0;
10842         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10843
10844         strcpy(bookMove, "move ");
10845         strcat(bookMove, bookHit);
10846         HandleMachineMove(bookMove, &first);
10847     }
10848 }
10849
10850
10851 void
10852 DisplayTwoMachinesTitle()
10853 {
10854     char buf[MSG_SIZ];
10855     if (appData.matchGames > 0) {
10856         if (first.twoMachinesColor[0] == 'w') {
10857             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10858                     gameInfo.white, gameInfo.black,
10859                     first.matchWins, second.matchWins,
10860                     matchGame - 1 - (first.matchWins + second.matchWins));
10861         } else {
10862             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10863                     gameInfo.white, gameInfo.black,
10864                     second.matchWins, first.matchWins,
10865                     matchGame - 1 - (first.matchWins + second.matchWins));
10866         }
10867     } else {
10868         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10869     }
10870     DisplayTitle(buf);
10871 }
10872
10873 void
10874 TwoMachinesEvent P((void))
10875 {
10876     int i;
10877     char buf[MSG_SIZ];
10878     ChessProgramState *onmove;
10879     char *bookHit = NULL;
10880     
10881     if (appData.noChessProgram) return;
10882
10883     switch (gameMode) {
10884       case TwoMachinesPlay:
10885         return;
10886       case MachinePlaysWhite:
10887       case MachinePlaysBlack:
10888         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10889             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10890             return;
10891         }
10892         /* fall through */
10893       case BeginningOfGame:
10894       case PlayFromGameFile:
10895       case EndOfGame:
10896         EditGameEvent();
10897         if (gameMode != EditGame) return;
10898         break;
10899       case EditPosition:
10900         EditPositionDone(TRUE);
10901         break;
10902       case AnalyzeMode:
10903       case AnalyzeFile:
10904         ExitAnalyzeMode();
10905         break;
10906       case EditGame:
10907       default:
10908         break;
10909     }
10910
10911     forwardMostMove = currentMove;
10912     ResurrectChessProgram();    /* in case first program isn't running */
10913
10914     if (second.pr == NULL) {
10915         StartChessProgram(&second);
10916         if (second.protocolVersion == 1) {
10917           TwoMachinesEventIfReady();
10918         } else {
10919           /* kludge: allow timeout for initial "feature" command */
10920           FreezeUI();
10921           DisplayMessage("", _("Starting second chess program"));
10922           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10923         }
10924         return;
10925     }
10926     DisplayMessage("", "");
10927     InitChessProgram(&second, FALSE);
10928     SendToProgram("force\n", &second);
10929     if (startedFromSetupPosition) {
10930         SendBoard(&second, backwardMostMove);
10931     if (appData.debugMode) {
10932         fprintf(debugFP, "Two Machines\n");
10933     }
10934     }
10935     for (i = backwardMostMove; i < forwardMostMove; i++) {
10936         SendMoveToProgram(i, &second);
10937     }
10938
10939     gameMode = TwoMachinesPlay;
10940     pausing = FALSE;
10941     ModeHighlight();
10942     SetGameInfo();
10943     DisplayTwoMachinesTitle();
10944     firstMove = TRUE;
10945     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10946         onmove = &first;
10947     } else {
10948         onmove = &second;
10949     }
10950
10951     SendToProgram(first.computerString, &first);
10952     if (first.sendName) {
10953       sprintf(buf, "name %s\n", second.tidy);
10954       SendToProgram(buf, &first);
10955     }
10956     SendToProgram(second.computerString, &second);
10957     if (second.sendName) {
10958       sprintf(buf, "name %s\n", first.tidy);
10959       SendToProgram(buf, &second);
10960     }
10961
10962     ResetClocks();
10963     if (!first.sendTime || !second.sendTime) {
10964         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10965         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10966     }
10967     if (onmove->sendTime) {
10968       if (onmove->useColors) {
10969         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10970       }
10971       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10972     }
10973     if (onmove->useColors) {
10974       SendToProgram(onmove->twoMachinesColor, onmove);
10975     }
10976     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10977 //    SendToProgram("go\n", onmove);
10978     onmove->maybeThinking = TRUE;
10979     SetMachineThinkingEnables();
10980
10981     StartClocks();
10982
10983     if(bookHit) { // [HGM] book: simulate book reply
10984         static char bookMove[MSG_SIZ]; // a bit generous?
10985
10986         programStats.nodes = programStats.depth = programStats.time = 
10987         programStats.score = programStats.got_only_move = 0;
10988         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10989
10990         strcpy(bookMove, "move ");
10991         strcat(bookMove, bookHit);
10992         savedMessage = bookMove; // args for deferred call
10993         savedState = onmove;
10994         ScheduleDelayedEvent(DeferredBookMove, 1);
10995     }
10996 }
10997
10998 void
10999 TrainingEvent()
11000 {
11001     if (gameMode == Training) {
11002       SetTrainingModeOff();
11003       gameMode = PlayFromGameFile;
11004       DisplayMessage("", _("Training mode off"));
11005     } else {
11006       gameMode = Training;
11007       animateTraining = appData.animate;
11008
11009       /* make sure we are not already at the end of the game */
11010       if (currentMove < forwardMostMove) {
11011         SetTrainingModeOn();
11012         DisplayMessage("", _("Training mode on"));
11013       } else {
11014         gameMode = PlayFromGameFile;
11015         DisplayError(_("Already at end of game"), 0);
11016       }
11017     }
11018     ModeHighlight();
11019 }
11020
11021 void
11022 IcsClientEvent()
11023 {
11024     if (!appData.icsActive) return;
11025     switch (gameMode) {
11026       case IcsPlayingWhite:
11027       case IcsPlayingBlack:
11028       case IcsObserving:
11029       case IcsIdle:
11030       case BeginningOfGame:
11031       case IcsExamining:
11032         return;
11033
11034       case EditGame:
11035         break;
11036
11037       case EditPosition:
11038         EditPositionDone(TRUE);
11039         break;
11040
11041       case AnalyzeMode:
11042       case AnalyzeFile:
11043         ExitAnalyzeMode();
11044         break;
11045         
11046       default:
11047         EditGameEvent();
11048         break;
11049     }
11050
11051     gameMode = IcsIdle;
11052     ModeHighlight();
11053     return;
11054 }
11055
11056
11057 void
11058 EditGameEvent()
11059 {
11060     int i;
11061
11062     switch (gameMode) {
11063       case Training:
11064         SetTrainingModeOff();
11065         break;
11066       case MachinePlaysWhite:
11067       case MachinePlaysBlack:
11068       case BeginningOfGame:
11069         SendToProgram("force\n", &first);
11070         SetUserThinkingEnables();
11071         break;
11072       case PlayFromGameFile:
11073         (void) StopLoadGameTimer();
11074         if (gameFileFP != NULL) {
11075             gameFileFP = NULL;
11076         }
11077         break;
11078       case EditPosition:
11079         EditPositionDone(TRUE);
11080         break;
11081       case AnalyzeMode:
11082       case AnalyzeFile:
11083         ExitAnalyzeMode();
11084         SendToProgram("force\n", &first);
11085         break;
11086       case TwoMachinesPlay:
11087         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11088         ResurrectChessProgram();
11089         SetUserThinkingEnables();
11090         break;
11091       case EndOfGame:
11092         ResurrectChessProgram();
11093         break;
11094       case IcsPlayingBlack:
11095       case IcsPlayingWhite:
11096         DisplayError(_("Warning: You are still playing a game"), 0);
11097         break;
11098       case IcsObserving:
11099         DisplayError(_("Warning: You are still observing a game"), 0);
11100         break;
11101       case IcsExamining:
11102         DisplayError(_("Warning: You are still examining a game"), 0);
11103         break;
11104       case IcsIdle:
11105         break;
11106       case EditGame:
11107       default:
11108         return;
11109     }
11110     
11111     pausing = FALSE;
11112     StopClocks();
11113     first.offeredDraw = second.offeredDraw = 0;
11114
11115     if (gameMode == PlayFromGameFile) {
11116         whiteTimeRemaining = timeRemaining[0][currentMove];
11117         blackTimeRemaining = timeRemaining[1][currentMove];
11118         DisplayTitle("");
11119     }
11120
11121     if (gameMode == MachinePlaysWhite ||
11122         gameMode == MachinePlaysBlack ||
11123         gameMode == TwoMachinesPlay ||
11124         gameMode == EndOfGame) {
11125         i = forwardMostMove;
11126         while (i > currentMove) {
11127             SendToProgram("undo\n", &first);
11128             i--;
11129         }
11130         whiteTimeRemaining = timeRemaining[0][currentMove];
11131         blackTimeRemaining = timeRemaining[1][currentMove];
11132         DisplayBothClocks();
11133         if (whiteFlag || blackFlag) {
11134             whiteFlag = blackFlag = 0;
11135         }
11136         DisplayTitle("");
11137     }           
11138     
11139     gameMode = EditGame;
11140     ModeHighlight();
11141     SetGameInfo();
11142 }
11143
11144
11145 void
11146 EditPositionEvent()
11147 {
11148     if (gameMode == EditPosition) {
11149         EditGameEvent();
11150         return;
11151     }
11152     
11153     EditGameEvent();
11154     if (gameMode != EditGame) return;
11155     
11156     gameMode = EditPosition;
11157     ModeHighlight();
11158     SetGameInfo();
11159     if (currentMove > 0)
11160       CopyBoard(boards[0], boards[currentMove]);
11161     
11162     blackPlaysFirst = !WhiteOnMove(currentMove);
11163     ResetClocks();
11164     currentMove = forwardMostMove = backwardMostMove = 0;
11165     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11166     DisplayMove(-1);
11167 }
11168
11169 void
11170 ExitAnalyzeMode()
11171 {
11172     /* [DM] icsEngineAnalyze - possible call from other functions */
11173     if (appData.icsEngineAnalyze) {
11174         appData.icsEngineAnalyze = FALSE;
11175
11176         DisplayMessage("",_("Close ICS engine analyze..."));
11177     }
11178     if (first.analysisSupport && first.analyzing) {
11179       SendToProgram("exit\n", &first);
11180       first.analyzing = FALSE;
11181     }
11182     thinkOutput[0] = NULLCHAR;
11183 }
11184
11185 void
11186 EditPositionDone(Boolean fakeRights)
11187 {
11188     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11189
11190     startedFromSetupPosition = TRUE;
11191     InitChessProgram(&first, FALSE);
11192     if(fakeRights)  
11193       { /* don't do this if we just pasted FEN */
11194         castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11195         if(boards[0][0][BOARD_WIDTH>>1] == king) 
11196           {
11197             castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11198             castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11199           } 
11200         else 
11201           castlingRights[0][2] = -1;
11202         if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) 
11203           {
11204             castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11205             castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11206           } 
11207         else 
11208           castlingRights[0][5] = -1;
11209       }
11210     SendToProgram("force\n", &first);
11211     if (blackPlaysFirst) {
11212         strcpy(moveList[0], "");
11213         strcpy(parseList[0], "");
11214         currentMove = forwardMostMove = backwardMostMove = 1;
11215         CopyBoard(boards[1], boards[0]);
11216         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11217         { int i;
11218           epStatus[1] = epStatus[0];
11219           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11220         }
11221     } else {
11222         currentMove = forwardMostMove = backwardMostMove = 0;
11223     }
11224     SendBoard(&first, forwardMostMove);
11225     if (appData.debugMode) {
11226         fprintf(debugFP, "EditPosDone\n");
11227     }
11228     DisplayTitle("");
11229     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11230     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11231     gameMode = EditGame;
11232     ModeHighlight();
11233     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11234     ClearHighlights(); /* [AS] */
11235 }
11236
11237 /* Pause for `ms' milliseconds */
11238 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11239 void
11240 TimeDelay(ms)
11241      long ms;
11242 {
11243     TimeMark m1, m2;
11244
11245     GetTimeMark(&m1);
11246     do {
11247         GetTimeMark(&m2);
11248     } while (SubtractTimeMarks(&m2, &m1) < ms);
11249 }
11250
11251 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11252 void
11253 SendMultiLineToICS(buf)
11254      char *buf;
11255 {
11256     char temp[MSG_SIZ+1], *p;
11257     int len;
11258
11259     len = strlen(buf);
11260     if (len > MSG_SIZ)
11261       len = MSG_SIZ;
11262   
11263     strncpy(temp, buf, len);
11264     temp[len] = 0;
11265
11266     p = temp;
11267     while (*p) {
11268         if (*p == '\n' || *p == '\r')
11269           *p = ' ';
11270         ++p;
11271     }
11272
11273     strcat(temp, "\n");
11274     SendToICS(temp);
11275     SendToPlayer(temp, strlen(temp));
11276 }
11277
11278 void
11279 SetWhiteToPlayEvent()
11280 {
11281     if (gameMode == EditPosition) {
11282         blackPlaysFirst = FALSE;
11283         DisplayBothClocks();    /* works because currentMove is 0 */
11284     } else if (gameMode == IcsExamining) {
11285         SendToICS(ics_prefix);
11286         SendToICS("tomove white\n");
11287     }
11288 }
11289
11290 void
11291 SetBlackToPlayEvent()
11292 {
11293     if (gameMode == EditPosition) {
11294         blackPlaysFirst = TRUE;
11295         currentMove = 1;        /* kludge */
11296         DisplayBothClocks();
11297         currentMove = 0;
11298     } else if (gameMode == IcsExamining) {
11299         SendToICS(ics_prefix);
11300         SendToICS("tomove black\n");
11301     }
11302 }
11303
11304 void
11305 EditPositionMenuEvent(selection, x, y)
11306      ChessSquare selection;
11307      int x, y;
11308 {
11309     char buf[MSG_SIZ];
11310     ChessSquare piece = boards[0][y][x];
11311
11312     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11313
11314     switch (selection) {
11315       case ClearBoard:
11316         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11317             SendToICS(ics_prefix);
11318             SendToICS("bsetup clear\n");
11319         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11320             SendToICS(ics_prefix);
11321             SendToICS("clearboard\n");
11322         } else {
11323             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11324                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11325                 for (y = 0; y < BOARD_HEIGHT; y++) {
11326                     if (gameMode == IcsExamining) {
11327                         if (boards[currentMove][y][x] != EmptySquare) {
11328                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11329                                     AAA + x, ONE + y);
11330                             SendToICS(buf);
11331                         }
11332                     } else {
11333                         boards[0][y][x] = p;
11334                     }
11335                 }
11336             }
11337         }
11338         if (gameMode == EditPosition) {
11339             DrawPosition(FALSE, boards[0]);
11340         }
11341         break;
11342
11343       case WhitePlay:
11344         SetWhiteToPlayEvent();
11345         break;
11346
11347       case BlackPlay:
11348         SetBlackToPlayEvent();
11349         break;
11350
11351       case EmptySquare:
11352         if (gameMode == IcsExamining) {
11353             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11354             SendToICS(buf);
11355         } else {
11356             boards[0][y][x] = EmptySquare;
11357             DrawPosition(FALSE, boards[0]);
11358         }
11359         break;
11360
11361       case PromotePiece:
11362         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11363            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11364             selection = (ChessSquare) (PROMOTED piece);
11365         } else if(piece == EmptySquare) selection = WhiteSilver;
11366         else selection = (ChessSquare)((int)piece - 1);
11367         goto defaultlabel;
11368
11369       case DemotePiece:
11370         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11371            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11372             selection = (ChessSquare) (DEMOTED piece);
11373         } else if(piece == EmptySquare) selection = BlackSilver;
11374         else selection = (ChessSquare)((int)piece + 1);       
11375         goto defaultlabel;
11376
11377       case WhiteQueen:
11378       case BlackQueen:
11379         if(gameInfo.variant == VariantShatranj ||
11380            gameInfo.variant == VariantXiangqi  ||
11381            gameInfo.variant == VariantCourier    )
11382             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11383         goto defaultlabel;
11384
11385       case WhiteKing:
11386       case BlackKing:
11387         if(gameInfo.variant == VariantXiangqi)
11388             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11389         if(gameInfo.variant == VariantKnightmate)
11390             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11391       default:
11392         defaultlabel:
11393         if (gameMode == IcsExamining) {
11394             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11395                     PieceToChar(selection), AAA + x, ONE + y);
11396             SendToICS(buf);
11397         } else {
11398             boards[0][y][x] = selection;
11399             DrawPosition(FALSE, boards[0]);
11400         }
11401         break;
11402     }
11403 }
11404
11405
11406 void
11407 DropMenuEvent(selection, x, y)
11408      ChessSquare selection;
11409      int x, y;
11410 {
11411     ChessMove moveType;
11412
11413     switch (gameMode) {
11414       case IcsPlayingWhite:
11415       case MachinePlaysBlack:
11416         if (!WhiteOnMove(currentMove)) {
11417             DisplayMoveError(_("It is Black's turn"));
11418             return;
11419         }
11420         moveType = WhiteDrop;
11421         break;
11422       case IcsPlayingBlack:
11423       case MachinePlaysWhite:
11424         if (WhiteOnMove(currentMove)) {
11425             DisplayMoveError(_("It is White's turn"));
11426             return;
11427         }
11428         moveType = BlackDrop;
11429         break;
11430       case EditGame:
11431         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11432         break;
11433       default:
11434         return;
11435     }
11436
11437     if (moveType == BlackDrop && selection < BlackPawn) {
11438       selection = (ChessSquare) ((int) selection
11439                                  + (int) BlackPawn - (int) WhitePawn);
11440     }
11441     if (boards[currentMove][y][x] != EmptySquare) {
11442         DisplayMoveError(_("That square is occupied"));
11443         return;
11444     }
11445
11446     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11447 }
11448
11449 void
11450 AcceptEvent()
11451 {
11452     /* Accept a pending offer of any kind from opponent */
11453     
11454     if (appData.icsActive) {
11455         SendToICS(ics_prefix);
11456         SendToICS("accept\n");
11457     } else if (cmailMsgLoaded) {
11458         if (currentMove == cmailOldMove &&
11459             commentList[cmailOldMove] != NULL &&
11460             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11461                    "Black offers a draw" : "White offers a draw")) {
11462             TruncateGame();
11463             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11464             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11465         } else {
11466             DisplayError(_("There is no pending offer on this move"), 0);
11467             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11468         }
11469     } else {
11470         /* Not used for offers from chess program */
11471     }
11472 }
11473
11474 void
11475 DeclineEvent()
11476 {
11477     /* Decline a pending offer of any kind from opponent */
11478     
11479     if (appData.icsActive) {
11480         SendToICS(ics_prefix);
11481         SendToICS("decline\n");
11482     } else if (cmailMsgLoaded) {
11483         if (currentMove == cmailOldMove &&
11484             commentList[cmailOldMove] != NULL &&
11485             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11486                    "Black offers a draw" : "White offers a draw")) {
11487 #ifdef NOTDEF
11488             AppendComment(cmailOldMove, "Draw declined");
11489             DisplayComment(cmailOldMove - 1, "Draw declined");
11490 #endif /*NOTDEF*/
11491         } else {
11492             DisplayError(_("There is no pending offer on this move"), 0);
11493         }
11494     } else {
11495         /* Not used for offers from chess program */
11496     }
11497 }
11498
11499 void
11500 RematchEvent()
11501 {
11502     /* Issue ICS rematch command */
11503     if (appData.icsActive) {
11504         SendToICS(ics_prefix);
11505         SendToICS("rematch\n");
11506     }
11507 }
11508
11509 void
11510 CallFlagEvent()
11511 {
11512     /* Call your opponent's flag (claim a win on time) */
11513     if (appData.icsActive) {
11514         SendToICS(ics_prefix);
11515         SendToICS("flag\n");
11516     } else {
11517         switch (gameMode) {
11518           default:
11519             return;
11520           case MachinePlaysWhite:
11521             if (whiteFlag) {
11522                 if (blackFlag)
11523                   GameEnds(GameIsDrawn, "Both players ran out of time",
11524                            GE_PLAYER);
11525                 else
11526                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11527             } else {
11528                 DisplayError(_("Your opponent is not out of time"), 0);
11529             }
11530             break;
11531           case MachinePlaysBlack:
11532             if (blackFlag) {
11533                 if (whiteFlag)
11534                   GameEnds(GameIsDrawn, "Both players ran out of time",
11535                            GE_PLAYER);
11536                 else
11537                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11538             } else {
11539                 DisplayError(_("Your opponent is not out of time"), 0);
11540             }
11541             break;
11542         }
11543     }
11544 }
11545
11546 void
11547 DrawEvent()
11548 {
11549     /* Offer draw or accept pending draw offer from opponent */
11550     
11551     if (appData.icsActive) {
11552         /* Note: tournament rules require draw offers to be
11553            made after you make your move but before you punch
11554            your clock.  Currently ICS doesn't let you do that;
11555            instead, you immediately punch your clock after making
11556            a move, but you can offer a draw at any time. */
11557         
11558         SendToICS(ics_prefix);
11559         SendToICS("draw\n");
11560     } else if (cmailMsgLoaded) {
11561         if (currentMove == cmailOldMove &&
11562             commentList[cmailOldMove] != NULL &&
11563             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11564                    "Black offers a draw" : "White offers a draw")) {
11565             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11566             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11567         } else if (currentMove == cmailOldMove + 1) {
11568             char *offer = WhiteOnMove(cmailOldMove) ?
11569               "White offers a draw" : "Black offers a draw";
11570             AppendComment(currentMove, offer);
11571             DisplayComment(currentMove - 1, offer);
11572             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11573         } else {
11574             DisplayError(_("You must make your move before offering a draw"), 0);
11575             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11576         }
11577     } else if (first.offeredDraw) {
11578         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11579     } else {
11580         if (first.sendDrawOffers) {
11581             SendToProgram("draw\n", &first);
11582             userOfferedDraw = TRUE;
11583         }
11584     }
11585 }
11586
11587 void
11588 AdjournEvent()
11589 {
11590     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11591     
11592     if (appData.icsActive) {
11593         SendToICS(ics_prefix);
11594         SendToICS("adjourn\n");
11595     } else {
11596         /* Currently GNU Chess doesn't offer or accept Adjourns */
11597     }
11598 }
11599
11600
11601 void
11602 AbortEvent()
11603 {
11604     /* Offer Abort or accept pending Abort offer from opponent */
11605     
11606     if (appData.icsActive) {
11607         SendToICS(ics_prefix);
11608         SendToICS("abort\n");
11609     } else {
11610         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11611     }
11612 }
11613
11614 void
11615 ResignEvent()
11616 {
11617     /* Resign.  You can do this even if it's not your turn. */
11618     
11619     if (appData.icsActive) {
11620         SendToICS(ics_prefix);
11621         SendToICS("resign\n");
11622     } else {
11623         switch (gameMode) {
11624           case MachinePlaysWhite:
11625             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11626             break;
11627           case MachinePlaysBlack:
11628             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11629             break;
11630           case EditGame:
11631             if (cmailMsgLoaded) {
11632                 TruncateGame();
11633                 if (WhiteOnMove(cmailOldMove)) {
11634                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11635                 } else {
11636                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11637                 }
11638                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11639             }
11640             break;
11641           default:
11642             break;
11643         }
11644     }
11645 }
11646
11647
11648 void
11649 StopObservingEvent()
11650 {
11651     /* Stop observing current games */
11652     SendToICS(ics_prefix);
11653     SendToICS("unobserve\n");
11654 }
11655
11656 void
11657 StopExaminingEvent()
11658 {
11659     /* Stop observing current game */
11660     SendToICS(ics_prefix);
11661     SendToICS("unexamine\n");
11662 }
11663
11664 void
11665 ForwardInner(target)
11666      int target;
11667 {
11668     int limit;
11669
11670     if (appData.debugMode)
11671         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11672                 target, currentMove, forwardMostMove);
11673
11674     if (gameMode == EditPosition)
11675       return;
11676
11677     if (gameMode == PlayFromGameFile && !pausing)
11678       PauseEvent();
11679     
11680     if (gameMode == IcsExamining && pausing)
11681       limit = pauseExamForwardMostMove;
11682     else
11683       limit = forwardMostMove;
11684     
11685     if (target > limit) target = limit;
11686
11687     if (target > 0 && moveList[target - 1][0]) {
11688         int fromX, fromY, toX, toY;
11689         toX = moveList[target - 1][2] - AAA;
11690         toY = moveList[target - 1][3] - ONE;
11691         if (moveList[target - 1][1] == '@') {
11692             if (appData.highlightLastMove) {
11693                 SetHighlights(-1, -1, toX, toY);
11694             }
11695         } else {
11696             fromX = moveList[target - 1][0] - AAA;
11697             fromY = moveList[target - 1][1] - ONE;
11698             if (target == currentMove + 1) {
11699                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11700             }
11701             if (appData.highlightLastMove) {
11702                 SetHighlights(fromX, fromY, toX, toY);
11703             }
11704         }
11705     }
11706     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11707         gameMode == Training || gameMode == PlayFromGameFile || 
11708         gameMode == AnalyzeFile) {
11709         while (currentMove < target) {
11710             SendMoveToProgram(currentMove++, &first);
11711         }
11712     } else {
11713         currentMove = target;
11714     }
11715     
11716     if (gameMode == EditGame || gameMode == EndOfGame) {
11717         whiteTimeRemaining = timeRemaining[0][currentMove];
11718         blackTimeRemaining = timeRemaining[1][currentMove];
11719     }
11720     DisplayBothClocks();
11721     DisplayMove(currentMove - 1);
11722     DrawPosition(FALSE, boards[currentMove]);
11723     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11724     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11725         DisplayComment(currentMove - 1, commentList[currentMove]);
11726     }
11727 }
11728
11729
11730 void
11731 ForwardEvent()
11732 {
11733     if (gameMode == IcsExamining && !pausing) {
11734         SendToICS(ics_prefix);
11735         SendToICS("forward\n");
11736     } else {
11737         ForwardInner(currentMove + 1);
11738     }
11739 }
11740
11741 void
11742 ToEndEvent()
11743 {
11744     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11745         /* to optimze, we temporarily turn off analysis mode while we feed
11746          * the remaining moves to the engine. Otherwise we get analysis output
11747          * after each move.
11748          */ 
11749         if (first.analysisSupport) {
11750           SendToProgram("exit\nforce\n", &first);
11751           first.analyzing = FALSE;
11752         }
11753     }
11754         
11755     if (gameMode == IcsExamining && !pausing) {
11756         SendToICS(ics_prefix);
11757         SendToICS("forward 999999\n");
11758     } else {
11759         ForwardInner(forwardMostMove);
11760     }
11761
11762     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11763         /* we have fed all the moves, so reactivate analysis mode */
11764         SendToProgram("analyze\n", &first);
11765         first.analyzing = TRUE;
11766         /*first.maybeThinking = TRUE;*/
11767         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11768     }
11769 }
11770
11771 void
11772 BackwardInner(target)
11773      int target;
11774 {
11775     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11776
11777     if (appData.debugMode)
11778         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11779                 target, currentMove, forwardMostMove);
11780
11781     if (gameMode == EditPosition) return;
11782     if (currentMove <= backwardMostMove) {
11783         ClearHighlights();
11784         DrawPosition(full_redraw, boards[currentMove]);
11785         return;
11786     }
11787     if (gameMode == PlayFromGameFile && !pausing)
11788       PauseEvent();
11789     
11790     if (moveList[target][0]) {
11791         int fromX, fromY, toX, toY;
11792         toX = moveList[target][2] - AAA;
11793         toY = moveList[target][3] - ONE;
11794         if (moveList[target][1] == '@') {
11795             if (appData.highlightLastMove) {
11796                 SetHighlights(-1, -1, toX, toY);
11797             }
11798         } else {
11799             fromX = moveList[target][0] - AAA;
11800             fromY = moveList[target][1] - ONE;
11801             if (target == currentMove - 1) {
11802                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11803             }
11804             if (appData.highlightLastMove) {
11805                 SetHighlights(fromX, fromY, toX, toY);
11806             }
11807         }
11808     }
11809     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11810         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11811         while (currentMove > target) {
11812             SendToProgram("undo\n", &first);
11813             currentMove--;
11814         }
11815     } else {
11816         currentMove = target;
11817     }
11818     
11819     if (gameMode == EditGame || gameMode == EndOfGame) {
11820         whiteTimeRemaining = timeRemaining[0][currentMove];
11821         blackTimeRemaining = timeRemaining[1][currentMove];
11822     }
11823     DisplayBothClocks();
11824     DisplayMove(currentMove - 1);
11825     DrawPosition(full_redraw, boards[currentMove]);
11826     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11827     // [HGM] PV info: routine tests if comment empty
11828     DisplayComment(currentMove - 1, commentList[currentMove]);
11829 }
11830
11831 void
11832 BackwardEvent()
11833 {
11834     if (gameMode == IcsExamining && !pausing) {
11835         SendToICS(ics_prefix);
11836         SendToICS("backward\n");
11837     } else {
11838         BackwardInner(currentMove - 1);
11839     }
11840 }
11841
11842 void
11843 ToStartEvent()
11844 {
11845     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11846         /* to optimize, we temporarily turn off analysis mode while we undo
11847          * all the moves. Otherwise we get analysis output after each undo.
11848          */ 
11849         if (first.analysisSupport) {
11850           SendToProgram("exit\nforce\n", &first);
11851           first.analyzing = FALSE;
11852         }
11853     }
11854
11855     if (gameMode == IcsExamining && !pausing) {
11856         SendToICS(ics_prefix);
11857         SendToICS("backward 999999\n");
11858     } else {
11859         BackwardInner(backwardMostMove);
11860     }
11861
11862     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11863         /* we have fed all the moves, so reactivate analysis mode */
11864         SendToProgram("analyze\n", &first);
11865         first.analyzing = TRUE;
11866         /*first.maybeThinking = TRUE;*/
11867         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11868     }
11869 }
11870
11871 void
11872 ToNrEvent(int to)
11873 {
11874   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11875   if (to >= forwardMostMove) to = forwardMostMove;
11876   if (to <= backwardMostMove) to = backwardMostMove;
11877   if (to < currentMove) {
11878     BackwardInner(to);
11879   } else {
11880     ForwardInner(to);
11881   }
11882 }
11883
11884 void
11885 RevertEvent()
11886 {
11887     if (gameMode != IcsExamining) {
11888         DisplayError(_("You are not examining a game"), 0);
11889         return;
11890     }
11891     if (pausing) {
11892         DisplayError(_("You can't revert while pausing"), 0);
11893         return;
11894     }
11895     SendToICS(ics_prefix);
11896     SendToICS("revert\n");
11897 }
11898
11899 void
11900 RetractMoveEvent()
11901 {
11902     switch (gameMode) {
11903       case MachinePlaysWhite:
11904       case MachinePlaysBlack:
11905         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11906             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11907             return;
11908         }
11909         if (forwardMostMove < 2) return;
11910         currentMove = forwardMostMove = forwardMostMove - 2;
11911         whiteTimeRemaining = timeRemaining[0][currentMove];
11912         blackTimeRemaining = timeRemaining[1][currentMove];
11913         DisplayBothClocks();
11914         DisplayMove(currentMove - 1);
11915         ClearHighlights();/*!! could figure this out*/
11916         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11917         SendToProgram("remove\n", &first);
11918         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11919         break;
11920
11921       case BeginningOfGame:
11922       default:
11923         break;
11924
11925       case IcsPlayingWhite:
11926       case IcsPlayingBlack:
11927         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11928             SendToICS(ics_prefix);
11929             SendToICS("takeback 2\n");
11930         } else {
11931             SendToICS(ics_prefix);
11932             SendToICS("takeback 1\n");
11933         }
11934         break;
11935     }
11936 }
11937
11938 void
11939 MoveNowEvent()
11940 {
11941     ChessProgramState *cps;
11942
11943     switch (gameMode) {
11944       case MachinePlaysWhite:
11945         if (!WhiteOnMove(forwardMostMove)) {
11946             DisplayError(_("It is your turn"), 0);
11947             return;
11948         }
11949         cps = &first;
11950         break;
11951       case MachinePlaysBlack:
11952         if (WhiteOnMove(forwardMostMove)) {
11953             DisplayError(_("It is your turn"), 0);
11954             return;
11955         }
11956         cps = &first;
11957         break;
11958       case TwoMachinesPlay:
11959         if (WhiteOnMove(forwardMostMove) ==
11960             (first.twoMachinesColor[0] == 'w')) {
11961             cps = &first;
11962         } else {
11963             cps = &second;
11964         }
11965         break;
11966       case BeginningOfGame:
11967       default:
11968         return;
11969     }
11970     SendToProgram("?\n", cps);
11971 }
11972
11973 void
11974 TruncateGameEvent()
11975 {
11976     EditGameEvent();
11977     if (gameMode != EditGame) return;
11978     TruncateGame();
11979 }
11980
11981 void
11982 TruncateGame()
11983 {
11984     if (forwardMostMove > currentMove) {
11985         if (gameInfo.resultDetails != NULL) {
11986             free(gameInfo.resultDetails);
11987             gameInfo.resultDetails = NULL;
11988             gameInfo.result = GameUnfinished;
11989         }
11990         forwardMostMove = currentMove;
11991         HistorySet(parseList, backwardMostMove, forwardMostMove,
11992                    currentMove-1);
11993     }
11994 }
11995
11996 void
11997 HintEvent()
11998 {
11999     if (appData.noChessProgram) return;
12000     switch (gameMode) {
12001       case MachinePlaysWhite:
12002         if (WhiteOnMove(forwardMostMove)) {
12003             DisplayError(_("Wait until your turn"), 0);
12004             return;
12005         }
12006         break;
12007       case BeginningOfGame:
12008       case MachinePlaysBlack:
12009         if (!WhiteOnMove(forwardMostMove)) {
12010             DisplayError(_("Wait until your turn"), 0);
12011             return;
12012         }
12013         break;
12014       default:
12015         DisplayError(_("No hint available"), 0);
12016         return;
12017     }
12018     SendToProgram("hint\n", &first);
12019     hintRequested = TRUE;
12020 }
12021
12022 void
12023 BookEvent()
12024 {
12025     if (appData.noChessProgram) return;
12026     switch (gameMode) {
12027       case MachinePlaysWhite:
12028         if (WhiteOnMove(forwardMostMove)) {
12029             DisplayError(_("Wait until your turn"), 0);
12030             return;
12031         }
12032         break;
12033       case BeginningOfGame:
12034       case MachinePlaysBlack:
12035         if (!WhiteOnMove(forwardMostMove)) {
12036             DisplayError(_("Wait until your turn"), 0);
12037             return;
12038         }
12039         break;
12040       case EditPosition:
12041         EditPositionDone(TRUE);
12042         break;
12043       case TwoMachinesPlay:
12044         return;
12045       default:
12046         break;
12047     }
12048     SendToProgram("bk\n", &first);
12049     bookOutput[0] = NULLCHAR;
12050     bookRequested = TRUE;
12051 }
12052
12053 void
12054 AboutGameEvent()
12055 {
12056     char *tags = PGNTags(&gameInfo);
12057     TagsPopUp(tags, CmailMsg());
12058     free(tags);
12059 }
12060
12061 /* end button procedures */
12062
12063 void
12064 PrintPosition(fp, move)
12065      FILE *fp;
12066      int move;
12067 {
12068     int i, j;
12069     
12070     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12071         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12072             char c = PieceToChar(boards[move][i][j]);
12073             fputc(c == 'x' ? '.' : c, fp);
12074             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12075         }
12076     }
12077     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12078       fprintf(fp, "white to play\n");
12079     else
12080       fprintf(fp, "black to play\n");
12081 }
12082
12083 void
12084 PrintOpponents(fp)
12085      FILE *fp;
12086 {
12087     if (gameInfo.white != NULL) {
12088         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12089     } else {
12090         fprintf(fp, "\n");
12091     }
12092 }
12093
12094 /* Find last component of program's own name, using some heuristics */
12095 void
12096 TidyProgramName(prog, host, buf)
12097      char *prog, *host, buf[MSG_SIZ];
12098 {
12099     char *p, *q;
12100     int local = (strcmp(host, "localhost") == 0);
12101     while (!local && (p = strchr(prog, ';')) != NULL) {
12102         p++;
12103         while (*p == ' ') p++;
12104         prog = p;
12105     }
12106     if (*prog == '"' || *prog == '\'') {
12107         q = strchr(prog + 1, *prog);
12108     } else {
12109         q = strchr(prog, ' ');
12110     }
12111     if (q == NULL) q = prog + strlen(prog);
12112     p = q;
12113     while (p >= prog && *p != '/' && *p != '\\') p--;
12114     p++;
12115     if(p == prog && *p == '"') p++;
12116     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12117     memcpy(buf, p, q - p);
12118     buf[q - p] = NULLCHAR;
12119     if (!local) {
12120         strcat(buf, "@");
12121         strcat(buf, host);
12122     }
12123 }
12124
12125 char *
12126 TimeControlTagValue()
12127 {
12128     char buf[MSG_SIZ];
12129     if (!appData.clockMode) {
12130         strcpy(buf, "-");
12131     } else if (movesPerSession > 0) {
12132         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12133     } else if (timeIncrement == 0) {
12134         sprintf(buf, "%ld", timeControl/1000);
12135     } else {
12136         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12137     }
12138     return StrSave(buf);
12139 }
12140
12141 void
12142 SetGameInfo()
12143 {
12144     /* This routine is used only for certain modes */
12145     VariantClass v = gameInfo.variant;
12146     ClearGameInfo(&gameInfo);
12147     gameInfo.variant = v;
12148
12149     switch (gameMode) {
12150       case MachinePlaysWhite:
12151         gameInfo.event = StrSave( appData.pgnEventHeader );
12152         gameInfo.site = StrSave(HostName());
12153         gameInfo.date = PGNDate();
12154         gameInfo.round = StrSave("-");
12155         gameInfo.white = StrSave(first.tidy);
12156         gameInfo.black = StrSave(UserName());
12157         gameInfo.timeControl = TimeControlTagValue();
12158         break;
12159
12160       case MachinePlaysBlack:
12161         gameInfo.event = StrSave( appData.pgnEventHeader );
12162         gameInfo.site = StrSave(HostName());
12163         gameInfo.date = PGNDate();
12164         gameInfo.round = StrSave("-");
12165         gameInfo.white = StrSave(UserName());
12166         gameInfo.black = StrSave(first.tidy);
12167         gameInfo.timeControl = TimeControlTagValue();
12168         break;
12169
12170       case TwoMachinesPlay:
12171         gameInfo.event = StrSave( appData.pgnEventHeader );
12172         gameInfo.site = StrSave(HostName());
12173         gameInfo.date = PGNDate();
12174         if (matchGame > 0) {
12175             char buf[MSG_SIZ];
12176             sprintf(buf, "%d", matchGame);
12177             gameInfo.round = StrSave(buf);
12178         } else {
12179             gameInfo.round = StrSave("-");
12180         }
12181         if (first.twoMachinesColor[0] == 'w') {
12182             gameInfo.white = StrSave(first.tidy);
12183             gameInfo.black = StrSave(second.tidy);
12184         } else {
12185             gameInfo.white = StrSave(second.tidy);
12186             gameInfo.black = StrSave(first.tidy);
12187         }
12188         gameInfo.timeControl = TimeControlTagValue();
12189         break;
12190
12191       case EditGame:
12192         gameInfo.event = StrSave("Edited game");
12193         gameInfo.site = StrSave(HostName());
12194         gameInfo.date = PGNDate();
12195         gameInfo.round = StrSave("-");
12196         gameInfo.white = StrSave("-");
12197         gameInfo.black = StrSave("-");
12198         break;
12199
12200       case EditPosition:
12201         gameInfo.event = StrSave("Edited position");
12202         gameInfo.site = StrSave(HostName());
12203         gameInfo.date = PGNDate();
12204         gameInfo.round = StrSave("-");
12205         gameInfo.white = StrSave("-");
12206         gameInfo.black = StrSave("-");
12207         break;
12208
12209       case IcsPlayingWhite:
12210       case IcsPlayingBlack:
12211       case IcsObserving:
12212       case IcsExamining:
12213         break;
12214
12215       case PlayFromGameFile:
12216         gameInfo.event = StrSave("Game from non-PGN file");
12217         gameInfo.site = StrSave(HostName());
12218         gameInfo.date = PGNDate();
12219         gameInfo.round = StrSave("-");
12220         gameInfo.white = StrSave("?");
12221         gameInfo.black = StrSave("?");
12222         break;
12223
12224       default:
12225         break;
12226     }
12227 }
12228
12229 void
12230 ReplaceComment(index, text)
12231      int index;
12232      char *text;
12233 {
12234     int len;
12235
12236     while (*text == '\n') text++;
12237     len = strlen(text);
12238     while (len > 0 && text[len - 1] == '\n') len--;
12239
12240     if (commentList[index] != NULL)
12241       free(commentList[index]);
12242
12243     if (len == 0) {
12244         commentList[index] = NULL;
12245         return;
12246     }
12247     commentList[index] = (char *) malloc(len + 2);
12248     strncpy(commentList[index], text, len);
12249     commentList[index][len] = '\n';
12250     commentList[index][len + 1] = NULLCHAR;
12251 }
12252
12253 void
12254 CrushCRs(text)
12255      char *text;
12256 {
12257   char *p = text;
12258   char *q = text;
12259   char ch;
12260
12261   do {
12262     ch = *p++;
12263     if (ch == '\r') continue;
12264     *q++ = ch;
12265   } while (ch != '\0');
12266 }
12267
12268 void
12269 AppendComment(index, text)
12270      int index;
12271      char *text;
12272 {
12273     int oldlen, len;
12274     char *old;
12275
12276     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12277
12278     CrushCRs(text);
12279     while (*text == '\n') text++;
12280     len = strlen(text);
12281     while (len > 0 && text[len - 1] == '\n') len--;
12282
12283     if (len == 0) return;
12284
12285     if (commentList[index] != NULL) {
12286         old = commentList[index];
12287         oldlen = strlen(old);
12288         commentList[index] = (char *) malloc(oldlen + len + 2);
12289         strcpy(commentList[index], old);
12290         free(old);
12291         strncpy(&commentList[index][oldlen], text, len);
12292         commentList[index][oldlen + len] = '\n';
12293         commentList[index][oldlen + len + 1] = NULLCHAR;
12294     } else {
12295         commentList[index] = (char *) malloc(len + 2);
12296         strncpy(commentList[index], text, len);
12297         commentList[index][len] = '\n';
12298         commentList[index][len + 1] = NULLCHAR;
12299     }
12300 }
12301
12302 static char * FindStr( char * text, char * sub_text )
12303 {
12304     char * result = strstr( text, sub_text );
12305
12306     if( result != NULL ) {
12307         result += strlen( sub_text );
12308     }
12309
12310     return result;
12311 }
12312
12313 /* [AS] Try to extract PV info from PGN comment */
12314 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12315 char *GetInfoFromComment( int index, char * text )
12316 {
12317     char * sep = text;
12318
12319     if( text != NULL && index > 0 ) {
12320         int score = 0;
12321         int depth = 0;
12322         int time = -1, sec = 0, deci;
12323         char * s_eval = FindStr( text, "[%eval " );
12324         char * s_emt = FindStr( text, "[%emt " );
12325
12326         if( s_eval != NULL || s_emt != NULL ) {
12327             /* New style */
12328             char delim;
12329
12330             if( s_eval != NULL ) {
12331                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12332                     return text;
12333                 }
12334
12335                 if( delim != ']' ) {
12336                     return text;
12337                 }
12338             }
12339
12340             if( s_emt != NULL ) {
12341             }
12342         }
12343         else {
12344             /* We expect something like: [+|-]nnn.nn/dd */
12345             int score_lo = 0;
12346
12347             sep = strchr( text, '/' );
12348             if( sep == NULL || sep < (text+4) ) {
12349                 return text;
12350             }
12351
12352             time = -1; sec = -1; deci = -1;
12353             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12354                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12355                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12356                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12357                 return text;
12358             }
12359
12360             if( score_lo < 0 || score_lo >= 100 ) {
12361                 return text;
12362             }
12363
12364             if(sec >= 0) time = 600*time + 10*sec; else
12365             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12366
12367             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12368
12369             /* [HGM] PV time: now locate end of PV info */
12370             while( *++sep >= '0' && *sep <= '9'); // strip depth
12371             if(time >= 0)
12372             while( *++sep >= '0' && *sep <= '9'); // strip time
12373             if(sec >= 0)
12374             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12375             if(deci >= 0)
12376             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12377             while(*sep == ' ') sep++;
12378         }
12379
12380         if( depth <= 0 ) {
12381             return text;
12382         }
12383
12384         if( time < 0 ) {
12385             time = -1;
12386         }
12387
12388         pvInfoList[index-1].depth = depth;
12389         pvInfoList[index-1].score = score;
12390         pvInfoList[index-1].time  = 10*time; // centi-sec
12391     }
12392     return sep;
12393 }
12394
12395 void
12396 SendToProgram(message, cps)
12397      char *message;
12398      ChessProgramState *cps;
12399 {
12400     int count, outCount, error;
12401     char buf[MSG_SIZ];
12402
12403     if (cps->pr == NULL) return;
12404     Attention(cps);
12405     
12406     if (appData.debugMode) {
12407         TimeMark now;
12408         GetTimeMark(&now);
12409         fprintf(debugFP, "%ld >%-6s: %s", 
12410                 SubtractTimeMarks(&now, &programStartTime),
12411                 cps->which, message);
12412     }
12413     
12414     count = strlen(message);
12415     outCount = OutputToProcess(cps->pr, message, count, &error);
12416     if (outCount < count && !exiting 
12417                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12418         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12419         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12420             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12421                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12422                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12423             } else {
12424                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12425             }
12426             gameInfo.resultDetails = StrSave(buf);
12427         }
12428         DisplayFatalError(buf, error, 1);
12429     }
12430 }
12431
12432 void
12433 ReceiveFromProgram(isr, closure, message, count, error)
12434      InputSourceRef isr;
12435      VOIDSTAR closure;
12436      char *message;
12437      int count;
12438      int error;
12439 {
12440     char *end_str;
12441     char buf[MSG_SIZ];
12442     ChessProgramState *cps = (ChessProgramState *)closure;
12443
12444     if (isr != cps->isr) return; /* Killed intentionally */
12445     if (count <= 0) {
12446         if (count == 0) {
12447             sprintf(buf,
12448                     _("Error: %s chess program (%s) exited unexpectedly"),
12449                     cps->which, cps->program);
12450         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12451                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12452                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12453                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12454                 } else {
12455                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12456                 }
12457                 gameInfo.resultDetails = StrSave(buf);
12458             }
12459             RemoveInputSource(cps->isr);
12460             DisplayFatalError(buf, 0, 1);
12461         } else {
12462             sprintf(buf,
12463                     _("Error reading from %s chess program (%s)"),
12464                     cps->which, cps->program);
12465             RemoveInputSource(cps->isr);
12466
12467             /* [AS] Program is misbehaving badly... kill it */
12468             if( count == -2 ) {
12469                 DestroyChildProcess( cps->pr, 9 );
12470                 cps->pr = NoProc;
12471             }
12472
12473             DisplayFatalError(buf, error, 1);
12474         }
12475         return;
12476     }
12477     
12478     if ((end_str = strchr(message, '\r')) != NULL)
12479       *end_str = NULLCHAR;
12480     if ((end_str = strchr(message, '\n')) != NULL)
12481       *end_str = NULLCHAR;
12482     
12483     if (appData.debugMode) {
12484         TimeMark now; int print = 1;
12485         char *quote = ""; char c; int i;
12486
12487         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12488                 char start = message[0];
12489                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12490                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12491                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12492                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12493                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12494                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12495                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12496                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12497                         { quote = "# "; print = (appData.engineComments == 2); }
12498                 message[0] = start; // restore original message
12499         }
12500         if(print) {
12501                 GetTimeMark(&now);
12502                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12503                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12504                         quote,
12505                         message);
12506         }
12507     }
12508
12509     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12510     if (appData.icsEngineAnalyze) {
12511         if (strstr(message, "whisper") != NULL ||
12512              strstr(message, "kibitz") != NULL || 
12513             strstr(message, "tellics") != NULL) return;
12514     }
12515
12516     HandleMachineMove(message, cps);
12517 }
12518
12519
12520 void
12521 SendTimeControl(cps, mps, tc, inc, sd, st)
12522      ChessProgramState *cps;
12523      int mps, inc, sd, st;
12524      long tc;
12525 {
12526     char buf[MSG_SIZ];
12527     int seconds;
12528
12529     if( timeControl_2 > 0 ) {
12530         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12531             tc = timeControl_2;
12532         }
12533     }
12534     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12535     inc /= cps->timeOdds;
12536     st  /= cps->timeOdds;
12537
12538     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12539
12540     if (st > 0) {
12541       /* Set exact time per move, normally using st command */
12542       if (cps->stKludge) {
12543         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12544         seconds = st % 60;
12545         if (seconds == 0) {
12546           sprintf(buf, "level 1 %d\n", st/60);
12547         } else {
12548           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12549         }
12550       } else {
12551         sprintf(buf, "st %d\n", st);
12552       }
12553     } else {
12554       /* Set conventional or incremental time control, using level command */
12555       if (seconds == 0) {
12556         /* Note old gnuchess bug -- minutes:seconds used to not work.
12557            Fixed in later versions, but still avoid :seconds
12558            when seconds is 0. */
12559         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12560       } else {
12561         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12562                 seconds, inc/1000);
12563       }
12564     }
12565     SendToProgram(buf, cps);
12566
12567     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12568     /* Orthogonally, limit search to given depth */
12569     if (sd > 0) {
12570       if (cps->sdKludge) {
12571         sprintf(buf, "depth\n%d\n", sd);
12572       } else {
12573         sprintf(buf, "sd %d\n", sd);
12574       }
12575       SendToProgram(buf, cps);
12576     }
12577
12578     if(cps->nps > 0) { /* [HGM] nps */
12579         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12580         else {
12581                 sprintf(buf, "nps %d\n", cps->nps);
12582               SendToProgram(buf, cps);
12583         }
12584     }
12585 }
12586
12587 ChessProgramState *WhitePlayer()
12588 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12589 {
12590     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12591        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12592         return &second;
12593     return &first;
12594 }
12595
12596 void
12597 SendTimeRemaining(cps, machineWhite)
12598      ChessProgramState *cps;
12599      int /*boolean*/ machineWhite;
12600 {
12601     char message[MSG_SIZ];
12602     long time, otime;
12603
12604     /* Note: this routine must be called when the clocks are stopped
12605        or when they have *just* been set or switched; otherwise
12606        it will be off by the time since the current tick started.
12607     */
12608     if (machineWhite) {
12609         time = whiteTimeRemaining / 10;
12610         otime = blackTimeRemaining / 10;
12611     } else {
12612         time = blackTimeRemaining / 10;
12613         otime = whiteTimeRemaining / 10;
12614     }
12615     /* [HGM] translate opponent's time by time-odds factor */
12616     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12617     if (appData.debugMode) {
12618         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12619     }
12620
12621     if (time <= 0) time = 1;
12622     if (otime <= 0) otime = 1;
12623     
12624     sprintf(message, "time %ld\n", time);
12625     SendToProgram(message, cps);
12626
12627     sprintf(message, "otim %ld\n", otime);
12628     SendToProgram(message, cps);
12629 }
12630
12631 int
12632 BoolFeature(p, name, loc, cps)
12633      char **p;
12634      char *name;
12635      int *loc;
12636      ChessProgramState *cps;
12637 {
12638   char buf[MSG_SIZ];
12639   int len = strlen(name);
12640   int val;
12641   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12642     (*p) += len + 1;
12643     sscanf(*p, "%d", &val);
12644     *loc = (val != 0);
12645     while (**p && **p != ' ') (*p)++;
12646     sprintf(buf, "accepted %s\n", name);
12647     SendToProgram(buf, cps);
12648     return TRUE;
12649   }
12650   return FALSE;
12651 }
12652
12653 int
12654 IntFeature(p, name, loc, cps)
12655      char **p;
12656      char *name;
12657      int *loc;
12658      ChessProgramState *cps;
12659 {
12660   char buf[MSG_SIZ];
12661   int len = strlen(name);
12662   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12663     (*p) += len + 1;
12664     sscanf(*p, "%d", loc);
12665     while (**p && **p != ' ') (*p)++;
12666     sprintf(buf, "accepted %s\n", name);
12667     SendToProgram(buf, cps);
12668     return TRUE;
12669   }
12670   return FALSE;
12671 }
12672
12673 int
12674 StringFeature(p, name, loc, cps)
12675      char **p;
12676      char *name;
12677      char loc[];
12678      ChessProgramState *cps;
12679 {
12680   char buf[MSG_SIZ];
12681   int len = strlen(name);
12682   if (strncmp((*p), name, len) == 0
12683       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12684     (*p) += len + 2;
12685     sscanf(*p, "%[^\"]", loc);
12686     while (**p && **p != '\"') (*p)++;
12687     if (**p == '\"') (*p)++;
12688     sprintf(buf, "accepted %s\n", name);
12689     SendToProgram(buf, cps);
12690     return TRUE;
12691   }
12692   return FALSE;
12693 }
12694
12695 int 
12696 ParseOption(Option *opt, ChessProgramState *cps)
12697 // [HGM] options: process the string that defines an engine option, and determine
12698 // name, type, default value, and allowed value range
12699 {
12700         char *p, *q, buf[MSG_SIZ];
12701         int n, min = (-1)<<31, max = 1<<31, def;
12702
12703         if(p = strstr(opt->name, " -spin ")) {
12704             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12705             if(max < min) max = min; // enforce consistency
12706             if(def < min) def = min;
12707             if(def > max) def = max;
12708             opt->value = def;
12709             opt->min = min;
12710             opt->max = max;
12711             opt->type = Spin;
12712         } else if((p = strstr(opt->name, " -slider "))) {
12713             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12714             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12715             if(max < min) max = min; // enforce consistency
12716             if(def < min) def = min;
12717             if(def > max) def = max;
12718             opt->value = def;
12719             opt->min = min;
12720             opt->max = max;
12721             opt->type = Spin; // Slider;
12722         } else if((p = strstr(opt->name, " -string "))) {
12723             opt->textValue = p+9;
12724             opt->type = TextBox;
12725         } else if((p = strstr(opt->name, " -file "))) {
12726             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12727             opt->textValue = p+7;
12728             opt->type = TextBox; // FileName;
12729         } else if((p = strstr(opt->name, " -path "))) {
12730             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12731             opt->textValue = p+7;
12732             opt->type = TextBox; // PathName;
12733         } else if(p = strstr(opt->name, " -check ")) {
12734             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12735             opt->value = (def != 0);
12736             opt->type = CheckBox;
12737         } else if(p = strstr(opt->name, " -combo ")) {
12738             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12739             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12740             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12741             opt->value = n = 0;
12742             while(q = StrStr(q, " /// ")) {
12743                 n++; *q = 0;    // count choices, and null-terminate each of them
12744                 q += 5;
12745                 if(*q == '*') { // remember default, which is marked with * prefix
12746                     q++;
12747                     opt->value = n;
12748                 }
12749                 cps->comboList[cps->comboCnt++] = q;
12750             }
12751             cps->comboList[cps->comboCnt++] = NULL;
12752             opt->max = n + 1;
12753             opt->type = ComboBox;
12754         } else if(p = strstr(opt->name, " -button")) {
12755             opt->type = Button;
12756         } else if(p = strstr(opt->name, " -save")) {
12757             opt->type = SaveButton;
12758         } else return FALSE;
12759         *p = 0; // terminate option name
12760         // now look if the command-line options define a setting for this engine option.
12761         if(cps->optionSettings && cps->optionSettings[0])
12762             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12763         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12764                 sprintf(buf, "option %s", p);
12765                 if(p = strstr(buf, ",")) *p = 0;
12766                 strcat(buf, "\n");
12767                 SendToProgram(buf, cps);
12768         }
12769         return TRUE;
12770 }
12771
12772 void
12773 FeatureDone(cps, val)
12774      ChessProgramState* cps;
12775      int val;
12776 {
12777   DelayedEventCallback cb = GetDelayedEvent();
12778   if ((cb == InitBackEnd3 && cps == &first) ||
12779       (cb == TwoMachinesEventIfReady && cps == &second)) {
12780     CancelDelayedEvent();
12781     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12782   }
12783   cps->initDone = val;
12784 }
12785
12786 /* Parse feature command from engine */
12787 void
12788 ParseFeatures(args, cps)
12789      char* args;
12790      ChessProgramState *cps;  
12791 {
12792   char *p = args;
12793   char *q;
12794   int val;
12795   char buf[MSG_SIZ];
12796
12797   for (;;) {
12798     while (*p == ' ') p++;
12799     if (*p == NULLCHAR) return;
12800
12801     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12802     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12803     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12804     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12805     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12806     if (BoolFeature(&p, "reuse", &val, cps)) {
12807       /* Engine can disable reuse, but can't enable it if user said no */
12808       if (!val) cps->reuse = FALSE;
12809       continue;
12810     }
12811     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12812     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12813       if (gameMode == TwoMachinesPlay) {
12814         DisplayTwoMachinesTitle();
12815       } else {
12816         DisplayTitle("");
12817       }
12818       continue;
12819     }
12820     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12821     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12822     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12823     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12824     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12825     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12826     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12827     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12828     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12829     if (IntFeature(&p, "done", &val, cps)) {
12830       FeatureDone(cps, val);
12831       continue;
12832     }
12833     /* Added by Tord: */
12834     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12835     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12836     /* End of additions by Tord */
12837
12838     /* [HGM] added features: */
12839     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12840     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12841     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12842     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12843     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12844     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12845     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12846         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12847             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12848             SendToProgram(buf, cps);
12849             continue;
12850         }
12851         if(cps->nrOptions >= MAX_OPTIONS) {
12852             cps->nrOptions--;
12853             sprintf(buf, "%s engine has too many options\n", cps->which);
12854             DisplayError(buf, 0);
12855         }
12856         continue;
12857     }
12858     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12859     /* End of additions by HGM */
12860
12861     /* unknown feature: complain and skip */
12862     q = p;
12863     while (*q && *q != '=') q++;
12864     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12865     SendToProgram(buf, cps);
12866     p = q;
12867     if (*p == '=') {
12868       p++;
12869       if (*p == '\"') {
12870         p++;
12871         while (*p && *p != '\"') p++;
12872         if (*p == '\"') p++;
12873       } else {
12874         while (*p && *p != ' ') p++;
12875       }
12876     }
12877   }
12878
12879 }
12880
12881 void
12882 PeriodicUpdatesEvent(newState)
12883      int newState;
12884 {
12885     if (newState == appData.periodicUpdates)
12886       return;
12887
12888     appData.periodicUpdates=newState;
12889
12890     /* Display type changes, so update it now */
12891 //    DisplayAnalysis();
12892
12893     /* Get the ball rolling again... */
12894     if (newState) {
12895         AnalysisPeriodicEvent(1);
12896         StartAnalysisClock();
12897     }
12898 }
12899
12900 void
12901 PonderNextMoveEvent(newState)
12902      int newState;
12903 {
12904     if (newState == appData.ponderNextMove) return;
12905     if (gameMode == EditPosition) EditPositionDone(TRUE);
12906     if (newState) {
12907         SendToProgram("hard\n", &first);
12908         if (gameMode == TwoMachinesPlay) {
12909             SendToProgram("hard\n", &second);
12910         }
12911     } else {
12912         SendToProgram("easy\n", &first);
12913         thinkOutput[0] = NULLCHAR;
12914         if (gameMode == TwoMachinesPlay) {
12915             SendToProgram("easy\n", &second);
12916         }
12917     }
12918     appData.ponderNextMove = newState;
12919 }
12920
12921 void
12922 NewSettingEvent(option, command, value)
12923      char *command;
12924      int option, value;
12925 {
12926     char buf[MSG_SIZ];
12927
12928     if (gameMode == EditPosition) EditPositionDone(TRUE);
12929     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12930     SendToProgram(buf, &first);
12931     if (gameMode == TwoMachinesPlay) {
12932         SendToProgram(buf, &second);
12933     }
12934 }
12935
12936 void
12937 ShowThinkingEvent()
12938 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12939 {
12940     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12941     int newState = appData.showThinking
12942         // [HGM] thinking: other features now need thinking output as well
12943         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12944     
12945     if (oldState == newState) return;
12946     oldState = newState;
12947     if (gameMode == EditPosition) EditPositionDone(TRUE);
12948     if (oldState) {
12949         SendToProgram("post\n", &first);
12950         if (gameMode == TwoMachinesPlay) {
12951             SendToProgram("post\n", &second);
12952         }
12953     } else {
12954         SendToProgram("nopost\n", &first);
12955         thinkOutput[0] = NULLCHAR;
12956         if (gameMode == TwoMachinesPlay) {
12957             SendToProgram("nopost\n", &second);
12958         }
12959     }
12960 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12961 }
12962
12963 void
12964 AskQuestionEvent(title, question, replyPrefix, which)
12965      char *title; char *question; char *replyPrefix; char *which;
12966 {
12967   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12968   if (pr == NoProc) return;
12969   AskQuestion(title, question, replyPrefix, pr);
12970 }
12971
12972 void
12973 DisplayMove(moveNumber)
12974      int moveNumber;
12975 {
12976     char message[MSG_SIZ];
12977     char res[MSG_SIZ];
12978     char cpThinkOutput[MSG_SIZ];
12979
12980     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12981     
12982     if (moveNumber == forwardMostMove - 1 || 
12983         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12984
12985         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12986
12987         if (strchr(cpThinkOutput, '\n')) {
12988             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12989         }
12990     } else {
12991         *cpThinkOutput = NULLCHAR;
12992     }
12993
12994     /* [AS] Hide thinking from human user */
12995     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12996         *cpThinkOutput = NULLCHAR;
12997         if( thinkOutput[0] != NULLCHAR ) {
12998             int i;
12999
13000             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13001                 cpThinkOutput[i] = '.';
13002             }
13003             cpThinkOutput[i] = NULLCHAR;
13004             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13005         }
13006     }
13007
13008     if (moveNumber == forwardMostMove - 1 &&
13009         gameInfo.resultDetails != NULL) {
13010         if (gameInfo.resultDetails[0] == NULLCHAR) {
13011             sprintf(res, " %s", PGNResult(gameInfo.result));
13012         } else {
13013             sprintf(res, " {%s} %s",
13014                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13015         }
13016     } else {
13017         res[0] = NULLCHAR;
13018     }
13019
13020     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13021         DisplayMessage(res, cpThinkOutput);
13022     } else {
13023         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13024                 WhiteOnMove(moveNumber) ? " " : ".. ",
13025                 parseList[moveNumber], res);
13026         DisplayMessage(message, cpThinkOutput);
13027     }
13028 }
13029
13030 void
13031 DisplayComment(moveNumber, text)
13032      int moveNumber;
13033      char *text;
13034 {
13035     char title[MSG_SIZ];
13036     char buf[8000]; // comment can be long!
13037     int score, depth;
13038     
13039     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13040       strcpy(title, "Comment");
13041     } else {
13042       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13043               WhiteOnMove(moveNumber) ? " " : ".. ",
13044               parseList[moveNumber]);
13045     }
13046     // [HGM] PV info: display PV info together with (or as) comment
13047     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13048       if(text == NULL) text = "";                                           
13049       score = pvInfoList[moveNumber].score;
13050       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13051               depth, (pvInfoList[moveNumber].time+50)/100, text);
13052       text = buf;
13053     }
13054     if (text != NULL && (appData.autoDisplayComment || commentUp))
13055         CommentPopUp(title, text);
13056 }
13057
13058 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13059  * might be busy thinking or pondering.  It can be omitted if your
13060  * gnuchess is configured to stop thinking immediately on any user
13061  * input.  However, that gnuchess feature depends on the FIONREAD
13062  * ioctl, which does not work properly on some flavors of Unix.
13063  */
13064 void
13065 Attention(cps)
13066      ChessProgramState *cps;
13067 {
13068 #if ATTENTION
13069     if (!cps->useSigint) return;
13070     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13071     switch (gameMode) {
13072       case MachinePlaysWhite:
13073       case MachinePlaysBlack:
13074       case TwoMachinesPlay:
13075       case IcsPlayingWhite:
13076       case IcsPlayingBlack:
13077       case AnalyzeMode:
13078       case AnalyzeFile:
13079         /* Skip if we know it isn't thinking */
13080         if (!cps->maybeThinking) return;
13081         if (appData.debugMode)
13082           fprintf(debugFP, "Interrupting %s\n", cps->which);
13083         InterruptChildProcess(cps->pr);
13084         cps->maybeThinking = FALSE;
13085         break;
13086       default:
13087         break;
13088     }
13089 #endif /*ATTENTION*/
13090 }
13091
13092 int
13093 CheckFlags()
13094 {
13095     if (whiteTimeRemaining <= 0) {
13096         if (!whiteFlag) {
13097             whiteFlag = TRUE;
13098             if (appData.icsActive) {
13099                 if (appData.autoCallFlag &&
13100                     gameMode == IcsPlayingBlack && !blackFlag) {
13101                   SendToICS(ics_prefix);
13102                   SendToICS("flag\n");
13103                 }
13104             } else {
13105                 if (blackFlag) {
13106                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13107                 } else {
13108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13109                     if (appData.autoCallFlag) {
13110                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13111                         return TRUE;
13112                     }
13113                 }
13114             }
13115         }
13116     }
13117     if (blackTimeRemaining <= 0) {
13118         if (!blackFlag) {
13119             blackFlag = TRUE;
13120             if (appData.icsActive) {
13121                 if (appData.autoCallFlag &&
13122                     gameMode == IcsPlayingWhite && !whiteFlag) {
13123                   SendToICS(ics_prefix);
13124                   SendToICS("flag\n");
13125                 }
13126             } else {
13127                 if (whiteFlag) {
13128                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13129                 } else {
13130                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13131                     if (appData.autoCallFlag) {
13132                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13133                         return TRUE;
13134                     }
13135                 }
13136             }
13137         }
13138     }
13139     return FALSE;
13140 }
13141
13142 void
13143 CheckTimeControl()
13144 {
13145     if (!appData.clockMode || appData.icsActive ||
13146         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13147
13148     /*
13149      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13150      */
13151     if ( !WhiteOnMove(forwardMostMove) )
13152         /* White made time control */
13153         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13154         /* [HGM] time odds: correct new time quota for time odds! */
13155                                             / WhitePlayer()->timeOdds;
13156       else
13157         /* Black made time control */
13158         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13159                                             / WhitePlayer()->other->timeOdds;
13160 }
13161
13162 void
13163 DisplayBothClocks()
13164 {
13165     int wom = gameMode == EditPosition ?
13166       !blackPlaysFirst : WhiteOnMove(currentMove);
13167     DisplayWhiteClock(whiteTimeRemaining, wom);
13168     DisplayBlackClock(blackTimeRemaining, !wom);
13169 }
13170
13171
13172 /* Timekeeping seems to be a portability nightmare.  I think everyone
13173    has ftime(), but I'm really not sure, so I'm including some ifdefs
13174    to use other calls if you don't.  Clocks will be less accurate if
13175    you have neither ftime nor gettimeofday.
13176 */
13177
13178 /* VS 2008 requires the #include outside of the function */
13179 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13180 #include <sys/timeb.h>
13181 #endif
13182
13183 /* Get the current time as a TimeMark */
13184 void
13185 GetTimeMark(tm)
13186      TimeMark *tm;
13187 {
13188 #if HAVE_GETTIMEOFDAY
13189
13190     struct timeval timeVal;
13191     struct timezone timeZone;
13192
13193     gettimeofday(&timeVal, &timeZone);
13194     tm->sec = (long) timeVal.tv_sec; 
13195     tm->ms = (int) (timeVal.tv_usec / 1000L);
13196
13197 #else /*!HAVE_GETTIMEOFDAY*/
13198 #if HAVE_FTIME
13199
13200 // include <sys/timeb.h> / moved to just above start of function
13201     struct timeb timeB;
13202
13203     ftime(&timeB);
13204     tm->sec = (long) timeB.time;
13205     tm->ms = (int) timeB.millitm;
13206
13207 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13208     tm->sec = (long) time(NULL);
13209     tm->ms = 0;
13210 #endif
13211 #endif
13212 }
13213
13214 /* Return the difference in milliseconds between two
13215    time marks.  We assume the difference will fit in a long!
13216 */
13217 long
13218 SubtractTimeMarks(tm2, tm1)
13219      TimeMark *tm2, *tm1;
13220 {
13221     return 1000L*(tm2->sec - tm1->sec) +
13222            (long) (tm2->ms - tm1->ms);
13223 }
13224
13225
13226 /*
13227  * Code to manage the game clocks.
13228  *
13229  * In tournament play, black starts the clock and then white makes a move.
13230  * We give the human user a slight advantage if he is playing white---the
13231  * clocks don't run until he makes his first move, so it takes zero time.
13232  * Also, we don't account for network lag, so we could get out of sync
13233  * with GNU Chess's clock -- but then, referees are always right.  
13234  */
13235
13236 static TimeMark tickStartTM;
13237 static long intendedTickLength;
13238
13239 long
13240 NextTickLength(timeRemaining)
13241      long timeRemaining;
13242 {
13243     long nominalTickLength, nextTickLength;
13244
13245     if (timeRemaining > 0L && timeRemaining <= 10000L)
13246       nominalTickLength = 100L;
13247     else
13248       nominalTickLength = 1000L;
13249     nextTickLength = timeRemaining % nominalTickLength;
13250     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13251
13252     return nextTickLength;
13253 }
13254
13255 /* Adjust clock one minute up or down */
13256 void
13257 AdjustClock(Boolean which, int dir)
13258 {
13259     if(which) blackTimeRemaining += 60000*dir;
13260     else      whiteTimeRemaining += 60000*dir;
13261     DisplayBothClocks();
13262 }
13263
13264 /* Stop clocks and reset to a fresh time control */
13265 void
13266 ResetClocks() 
13267 {
13268     (void) StopClockTimer();
13269     if (appData.icsActive) {
13270         whiteTimeRemaining = blackTimeRemaining = 0;
13271     } else { /* [HGM] correct new time quote for time odds */
13272         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13273         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13274     }
13275     if (whiteFlag || blackFlag) {
13276         DisplayTitle("");
13277         whiteFlag = blackFlag = FALSE;
13278     }
13279     DisplayBothClocks();
13280 }
13281
13282 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13283
13284 /* Decrement running clock by amount of time that has passed */
13285 void
13286 DecrementClocks()
13287 {
13288     long timeRemaining;
13289     long lastTickLength, fudge;
13290     TimeMark now;
13291
13292     if (!appData.clockMode) return;
13293     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13294         
13295     GetTimeMark(&now);
13296
13297     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13298
13299     /* Fudge if we woke up a little too soon */
13300     fudge = intendedTickLength - lastTickLength;
13301     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13302
13303     if (WhiteOnMove(forwardMostMove)) {
13304         if(whiteNPS >= 0) lastTickLength = 0;
13305         timeRemaining = whiteTimeRemaining -= lastTickLength;
13306         DisplayWhiteClock(whiteTimeRemaining - fudge,
13307                           WhiteOnMove(currentMove));
13308     } else {
13309         if(blackNPS >= 0) lastTickLength = 0;
13310         timeRemaining = blackTimeRemaining -= lastTickLength;
13311         DisplayBlackClock(blackTimeRemaining - fudge,
13312                           !WhiteOnMove(currentMove));
13313     }
13314
13315     if (CheckFlags()) return;
13316         
13317     tickStartTM = now;
13318     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13319     StartClockTimer(intendedTickLength);
13320
13321     /* if the time remaining has fallen below the alarm threshold, sound the
13322      * alarm. if the alarm has sounded and (due to a takeback or time control
13323      * with increment) the time remaining has increased to a level above the
13324      * threshold, reset the alarm so it can sound again. 
13325      */
13326     
13327     if (appData.icsActive && appData.icsAlarm) {
13328
13329         /* make sure we are dealing with the user's clock */
13330         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13331                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13332            )) return;
13333
13334         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13335             alarmSounded = FALSE;
13336         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13337             PlayAlarmSound();
13338             alarmSounded = TRUE;
13339         }
13340     }
13341 }
13342
13343
13344 /* A player has just moved, so stop the previously running
13345    clock and (if in clock mode) start the other one.
13346    We redisplay both clocks in case we're in ICS mode, because
13347    ICS gives us an update to both clocks after every move.
13348    Note that this routine is called *after* forwardMostMove
13349    is updated, so the last fractional tick must be subtracted
13350    from the color that is *not* on move now.
13351 */
13352 void
13353 SwitchClocks()
13354 {
13355     long lastTickLength;
13356     TimeMark now;
13357     int flagged = FALSE;
13358
13359     GetTimeMark(&now);
13360
13361     if (StopClockTimer() && appData.clockMode) {
13362         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13363         if (WhiteOnMove(forwardMostMove)) {
13364             if(blackNPS >= 0) lastTickLength = 0;
13365             blackTimeRemaining -= lastTickLength;
13366            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13367 //         if(pvInfoList[forwardMostMove-1].time == -1)
13368                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13369                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13370         } else {
13371            if(whiteNPS >= 0) lastTickLength = 0;
13372            whiteTimeRemaining -= lastTickLength;
13373            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13374 //         if(pvInfoList[forwardMostMove-1].time == -1)
13375                  pvInfoList[forwardMostMove-1].time = 
13376                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13377         }
13378         flagged = CheckFlags();
13379     }
13380     CheckTimeControl();
13381
13382     if (flagged || !appData.clockMode) return;
13383
13384     switch (gameMode) {
13385       case MachinePlaysBlack:
13386       case MachinePlaysWhite:
13387       case BeginningOfGame:
13388         if (pausing) return;
13389         break;
13390
13391       case EditGame:
13392       case PlayFromGameFile:
13393       case IcsExamining:
13394         return;
13395
13396       default:
13397         break;
13398     }
13399
13400     tickStartTM = now;
13401     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13402       whiteTimeRemaining : blackTimeRemaining);
13403     StartClockTimer(intendedTickLength);
13404 }
13405         
13406
13407 /* Stop both clocks */
13408 void
13409 StopClocks()
13410 {       
13411     long lastTickLength;
13412     TimeMark now;
13413
13414     if (!StopClockTimer()) return;
13415     if (!appData.clockMode) return;
13416
13417     GetTimeMark(&now);
13418
13419     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13420     if (WhiteOnMove(forwardMostMove)) {
13421         if(whiteNPS >= 0) lastTickLength = 0;
13422         whiteTimeRemaining -= lastTickLength;
13423         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13424     } else {
13425         if(blackNPS >= 0) lastTickLength = 0;
13426         blackTimeRemaining -= lastTickLength;
13427         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13428     }
13429     CheckFlags();
13430 }
13431         
13432 /* Start clock of player on move.  Time may have been reset, so
13433    if clock is already running, stop and restart it. */
13434 void
13435 StartClocks()
13436 {
13437     (void) StopClockTimer(); /* in case it was running already */
13438     DisplayBothClocks();
13439     if (CheckFlags()) return;
13440
13441     if (!appData.clockMode) return;
13442     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13443
13444     GetTimeMark(&tickStartTM);
13445     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13446       whiteTimeRemaining : blackTimeRemaining);
13447
13448    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13449     whiteNPS = blackNPS = -1; 
13450     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13451        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13452         whiteNPS = first.nps;
13453     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13454        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13455         blackNPS = first.nps;
13456     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13457         whiteNPS = second.nps;
13458     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13459         blackNPS = second.nps;
13460     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13461
13462     StartClockTimer(intendedTickLength);
13463 }
13464
13465 char *
13466 TimeString(ms)
13467      long ms;
13468 {
13469     long second, minute, hour, day;
13470     char *sign = "";
13471     static char buf[32];
13472     
13473     if (ms > 0 && ms <= 9900) {
13474       /* convert milliseconds to tenths, rounding up */
13475       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13476
13477       sprintf(buf, " %03.1f ", tenths/10.0);
13478       return buf;
13479     }
13480
13481     /* convert milliseconds to seconds, rounding up */
13482     /* use floating point to avoid strangeness of integer division
13483        with negative dividends on many machines */
13484     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13485
13486     if (second < 0) {
13487         sign = "-";
13488         second = -second;
13489     }
13490     
13491     day = second / (60 * 60 * 24);
13492     second = second % (60 * 60 * 24);
13493     hour = second / (60 * 60);
13494     second = second % (60 * 60);
13495     minute = second / 60;
13496     second = second % 60;
13497     
13498     if (day > 0)
13499       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13500               sign, day, hour, minute, second);
13501     else if (hour > 0)
13502       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13503     else
13504       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13505     
13506     return buf;
13507 }
13508
13509
13510 /*
13511  * This is necessary because some C libraries aren't ANSI C compliant yet.
13512  */
13513 char *
13514 StrStr(string, match)
13515      char *string, *match;
13516 {
13517     int i, length;
13518     
13519     length = strlen(match);
13520     
13521     for (i = strlen(string) - length; i >= 0; i--, string++)
13522       if (!strncmp(match, string, length))
13523         return string;
13524     
13525     return NULL;
13526 }
13527
13528 char *
13529 StrCaseStr(string, match)
13530      char *string, *match;
13531 {
13532     int i, j, length;
13533     
13534     length = strlen(match);
13535     
13536     for (i = strlen(string) - length; i >= 0; i--, string++) {
13537         for (j = 0; j < length; j++) {
13538             if (ToLower(match[j]) != ToLower(string[j]))
13539               break;
13540         }
13541         if (j == length) return string;
13542     }
13543
13544     return NULL;
13545 }
13546
13547 #ifndef _amigados
13548 int
13549 StrCaseCmp(s1, s2)
13550      char *s1, *s2;
13551 {
13552     char c1, c2;
13553     
13554     for (;;) {
13555         c1 = ToLower(*s1++);
13556         c2 = ToLower(*s2++);
13557         if (c1 > c2) return 1;
13558         if (c1 < c2) return -1;
13559         if (c1 == NULLCHAR) return 0;
13560     }
13561 }
13562
13563
13564 int
13565 ToLower(c)
13566      int c;
13567 {
13568     return isupper(c) ? tolower(c) : c;
13569 }
13570
13571
13572 int
13573 ToUpper(c)
13574      int c;
13575 {
13576     return islower(c) ? toupper(c) : c;
13577 }
13578 #endif /* !_amigados    */
13579
13580 char *
13581 StrSave(s)
13582      char *s;
13583 {
13584     char *ret;
13585
13586     if ((ret = (char *) malloc(strlen(s) + 1))) {
13587         strcpy(ret, s);
13588     }
13589     return ret;
13590 }
13591
13592 char *
13593 StrSavePtr(s, savePtr)
13594      char *s, **savePtr;
13595 {
13596     if (*savePtr) {
13597         free(*savePtr);
13598     }
13599     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13600         strcpy(*savePtr, s);
13601     }
13602     return(*savePtr);
13603 }
13604
13605 char *
13606 PGNDate()
13607 {
13608     time_t clock;
13609     struct tm *tm;
13610     char buf[MSG_SIZ];
13611
13612     clock = time((time_t *)NULL);
13613     tm = localtime(&clock);
13614     sprintf(buf, "%04d.%02d.%02d",
13615             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13616     return StrSave(buf);
13617 }
13618
13619
13620 char *
13621 PositionToFEN(move, overrideCastling)
13622      int move;
13623      char *overrideCastling;
13624 {
13625     int i, j, fromX, fromY, toX, toY;
13626     int whiteToPlay;
13627     char buf[128];
13628     char *p, *q;
13629     int emptycount;
13630     ChessSquare piece;
13631
13632     whiteToPlay = (gameMode == EditPosition) ?
13633       !blackPlaysFirst : (move % 2 == 0);
13634     p = buf;
13635
13636     /* Piece placement data */
13637     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13638         emptycount = 0;
13639         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13640             if (boards[move][i][j] == EmptySquare) {
13641                 emptycount++;
13642             } else { ChessSquare piece = boards[move][i][j];
13643                 if (emptycount > 0) {
13644                     if(emptycount<10) /* [HGM] can be >= 10 */
13645                         *p++ = '0' + emptycount;
13646                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13647                     emptycount = 0;
13648                 }
13649                 if(PieceToChar(piece) == '+') {
13650                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13651                     *p++ = '+';
13652                     piece = (ChessSquare)(DEMOTED piece);
13653                 } 
13654                 *p++ = PieceToChar(piece);
13655                 if(p[-1] == '~') {
13656                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13657                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13658                     *p++ = '~';
13659                 }
13660             }
13661         }
13662         if (emptycount > 0) {
13663             if(emptycount<10) /* [HGM] can be >= 10 */
13664                 *p++ = '0' + emptycount;
13665             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13666             emptycount = 0;
13667         }
13668         *p++ = '/';
13669     }
13670     *(p - 1) = ' ';
13671
13672     /* [HGM] print Crazyhouse or Shogi holdings */
13673     if( gameInfo.holdingsWidth ) {
13674         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13675         q = p;
13676         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13677             piece = boards[move][i][BOARD_WIDTH-1];
13678             if( piece != EmptySquare )
13679               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13680                   *p++ = PieceToChar(piece);
13681         }
13682         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13683             piece = boards[move][BOARD_HEIGHT-i-1][0];
13684             if( piece != EmptySquare )
13685               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13686                   *p++ = PieceToChar(piece);
13687         }
13688
13689         if( q == p ) *p++ = '-';
13690         *p++ = ']';
13691         *p++ = ' ';
13692     }
13693
13694     /* Active color */
13695     *p++ = whiteToPlay ? 'w' : 'b';
13696     *p++ = ' ';
13697
13698   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13699     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13700   } else {
13701   if(nrCastlingRights) {
13702      q = p;
13703      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13704        /* [HGM] write directly from rights */
13705            if(castlingRights[move][2] >= 0 &&
13706               castlingRights[move][0] >= 0   )
13707                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13708            if(castlingRights[move][2] >= 0 &&
13709               castlingRights[move][1] >= 0   )
13710                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13711            if(castlingRights[move][5] >= 0 &&
13712               castlingRights[move][3] >= 0   )
13713                 *p++ = castlingRights[move][3] + AAA;
13714            if(castlingRights[move][5] >= 0 &&
13715               castlingRights[move][4] >= 0   )
13716                 *p++ = castlingRights[move][4] + AAA;
13717      } else {
13718
13719         /* [HGM] write true castling rights */
13720         if( nrCastlingRights == 6 ) {
13721             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13722                castlingRights[move][2] >= 0  ) *p++ = 'K';
13723             if(castlingRights[move][1] == BOARD_LEFT &&
13724                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13725             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13726                castlingRights[move][5] >= 0  ) *p++ = 'k';
13727             if(castlingRights[move][4] == BOARD_LEFT &&
13728                castlingRights[move][5] >= 0  ) *p++ = 'q';
13729         }
13730      }
13731      if (q == p) *p++ = '-'; /* No castling rights */
13732      *p++ = ' ';
13733   }
13734
13735   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13736      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13737     /* En passant target square */
13738     if (move > backwardMostMove) {
13739         fromX = moveList[move - 1][0] - AAA;
13740         fromY = moveList[move - 1][1] - ONE;
13741         toX = moveList[move - 1][2] - AAA;
13742         toY = moveList[move - 1][3] - ONE;
13743         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13744             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13745             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13746             fromX == toX) {
13747             /* 2-square pawn move just happened */
13748             *p++ = toX + AAA;
13749             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13750         } else {
13751             *p++ = '-';
13752         }
13753     } else if(move == backwardMostMove) {
13754         // [HGM] perhaps we should always do it like this, and forget the above?
13755         if(epStatus[move] >= 0) {
13756             *p++ = epStatus[move] + AAA;
13757             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13758         } else {
13759             *p++ = '-';
13760         }
13761     } else {
13762         *p++ = '-';
13763     }
13764     *p++ = ' ';
13765   }
13766   }
13767
13768     /* [HGM] find reversible plies */
13769     {   int i = 0, j=move;
13770
13771         if (appData.debugMode) { int k;
13772             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13773             for(k=backwardMostMove; k<=forwardMostMove; k++)
13774                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13775
13776         }
13777
13778         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13779         if( j == backwardMostMove ) i += initialRulePlies;
13780         sprintf(p, "%d ", i);
13781         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13782     }
13783     /* Fullmove number */
13784     sprintf(p, "%d", (move / 2) + 1);
13785     
13786     return StrSave(buf);
13787 }
13788
13789 Boolean
13790 ParseFEN(board, blackPlaysFirst, fen)
13791     Board board;
13792      int *blackPlaysFirst;
13793      char *fen;
13794 {
13795     int i, j;
13796     char *p;
13797     int emptycount;
13798     ChessSquare piece;
13799
13800     p = fen;
13801
13802     /* [HGM] by default clear Crazyhouse holdings, if present */
13803     if(gameInfo.holdingsWidth) {
13804        for(i=0; i<BOARD_HEIGHT; i++) {
13805            board[i][0]             = EmptySquare; /* black holdings */
13806            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13807            board[i][1]             = (ChessSquare) 0; /* black counts */
13808            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13809        }
13810     }
13811
13812     /* Piece placement data */
13813     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13814         j = 0;
13815         for (;;) {
13816             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13817                 if (*p == '/') p++;
13818                 emptycount = gameInfo.boardWidth - j;
13819                 while (emptycount--)
13820                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13821                 break;
13822 #if(BOARD_SIZE >= 10)
13823             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13824                 p++; emptycount=10;
13825                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13826                 while (emptycount--)
13827                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13828 #endif
13829             } else if (isdigit(*p)) {
13830                 emptycount = *p++ - '0';
13831                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13832                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13833                 while (emptycount--)
13834                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13835             } else if (*p == '+' || isalpha(*p)) {
13836                 if (j >= gameInfo.boardWidth) return FALSE;
13837                 if(*p=='+') {
13838                     piece = CharToPiece(*++p);
13839                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13840                     piece = (ChessSquare) (PROMOTED piece ); p++;
13841                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13842                 } else piece = CharToPiece(*p++);
13843
13844                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13845                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13846                     piece = (ChessSquare) (PROMOTED piece);
13847                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13848                     p++;
13849                 }
13850                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13851             } else {
13852                 return FALSE;
13853             }
13854         }
13855     }
13856     while (*p == '/' || *p == ' ') p++;
13857
13858     /* [HGM] look for Crazyhouse holdings here */
13859     while(*p==' ') p++;
13860     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13861         if(*p == '[') p++;
13862         if(*p == '-' ) *p++; /* empty holdings */ else {
13863             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13864             /* if we would allow FEN reading to set board size, we would   */
13865             /* have to add holdings and shift the board read so far here   */
13866             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13867                 *p++;
13868                 if((int) piece >= (int) BlackPawn ) {
13869                     i = (int)piece - (int)BlackPawn;
13870                     i = PieceToNumber((ChessSquare)i);
13871                     if( i >= gameInfo.holdingsSize ) return FALSE;
13872                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13873                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13874                 } else {
13875                     i = (int)piece - (int)WhitePawn;
13876                     i = PieceToNumber((ChessSquare)i);
13877                     if( i >= gameInfo.holdingsSize ) return FALSE;
13878                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13879                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13880                 }
13881             }
13882         }
13883         if(*p == ']') *p++;
13884     }
13885
13886     while(*p == ' ') p++;
13887
13888     /* Active color */
13889     switch (*p++) {
13890       case 'w':
13891         *blackPlaysFirst = FALSE;
13892         break;
13893       case 'b': 
13894         *blackPlaysFirst = TRUE;
13895         break;
13896       default:
13897         return FALSE;
13898     }
13899
13900     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13901     /* return the extra info in global variiables             */
13902
13903     /* set defaults in case FEN is incomplete */
13904     FENepStatus = EP_UNKNOWN;
13905     for(i=0; i<nrCastlingRights; i++ ) {
13906         FENcastlingRights[i] =
13907             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13908     }   /* assume possible unless obviously impossible */
13909     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13910     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13911     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
13912                            && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13913     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13914     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13915     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
13916                            && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13917     FENrulePlies = 0;
13918
13919     while(*p==' ') p++;
13920     if(nrCastlingRights) {
13921       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13922           /* castling indicator present, so default becomes no castlings */
13923           for(i=0; i<nrCastlingRights; i++ ) {
13924                  FENcastlingRights[i] = -1;
13925           }
13926       }
13927       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13928              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13929              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13930              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13931         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13932
13933         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13934             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13935             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13936         }
13937         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
13938             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // scanning fails in these variants
13939         if(whiteKingFile<0 || board[0][whiteKingFile]!=WhiteUnicorn
13940                            && board[0][whiteKingFile]!=WhiteKing) whiteKingFile = -1;
13941         if(blackKingFile<0 || board[BOARD_HEIGHT-1][blackKingFile]!=BlackUnicorn
13942                            && board[BOARD_HEIGHT-1][blackKingFile]!=BlackKing) blackKingFile = -1;
13943         switch(c) {
13944           case'K':
13945               for(i=BOARD_RGHT-1; i>whiteKingFile && board[0][i]!=WhiteRook; i--);
13946               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13947               FENcastlingRights[2] = whiteKingFile;
13948               break;
13949           case'Q':
13950               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13951               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13952               FENcastlingRights[2] = whiteKingFile;
13953               break;
13954           case'k':
13955               for(i=BOARD_RGHT-1; i>blackKingFile && board[BOARD_HEIGHT-1][i]!=BlackRook; i--);
13956               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13957               FENcastlingRights[5] = blackKingFile;
13958               break;
13959           case'q':
13960               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13961               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13962               FENcastlingRights[5] = blackKingFile;
13963           case '-':
13964               break;
13965           default: /* FRC castlings */
13966               if(c >= 'a') { /* black rights */
13967                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13968                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13969                   if(i == BOARD_RGHT) break;
13970                   FENcastlingRights[5] = i;
13971                   c -= AAA;
13972                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13973                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13974                   if(c > i)
13975                       FENcastlingRights[3] = c;
13976                   else
13977                       FENcastlingRights[4] = c;
13978               } else { /* white rights */
13979                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13980                     if(board[0][i] == WhiteKing) break;
13981                   if(i == BOARD_RGHT) break;
13982                   FENcastlingRights[2] = i;
13983                   c -= AAA - 'a' + 'A';
13984                   if(board[0][c] >= WhiteKing) break;
13985                   if(c > i)
13986                       FENcastlingRights[0] = c;
13987                   else
13988                       FENcastlingRights[1] = c;
13989               }
13990         }
13991       }
13992       for(i=0; i<nrCastlingRights; i++)
13993         if(FENcastlingRights[i] >= 0) initialRights[i] = FENcastlingRights[i];
13994     if (appData.debugMode) {
13995         fprintf(debugFP, "FEN castling rights:");
13996         for(i=0; i<nrCastlingRights; i++)
13997         fprintf(debugFP, " %d", FENcastlingRights[i]);
13998         fprintf(debugFP, "\n");
13999     }
14000
14001       while(*p==' ') p++;
14002     }
14003
14004     /* read e.p. field in games that know e.p. capture */
14005     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14006        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14007       if(*p=='-') {
14008         p++; FENepStatus = EP_NONE;
14009       } else {
14010          char c = *p++ - AAA;
14011
14012          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14013          if(*p >= '0' && *p <='9') *p++;
14014          FENepStatus = c;
14015       }
14016     }
14017
14018
14019     if(sscanf(p, "%d", &i) == 1) {
14020         FENrulePlies = i; /* 50-move ply counter */
14021         /* (The move number is still ignored)    */
14022     }
14023
14024     return TRUE;
14025 }
14026       
14027 void
14028 EditPositionPasteFEN(char *fen)
14029 {
14030   if (fen != NULL) {
14031     Board initial_position;
14032
14033     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14034       DisplayError(_("Bad FEN position in clipboard"), 0);
14035       return ;
14036     } else {
14037       int savedBlackPlaysFirst = blackPlaysFirst;
14038       EditPositionEvent();
14039       blackPlaysFirst = savedBlackPlaysFirst;
14040       CopyBoard(boards[0], initial_position);
14041           /* [HGM] copy FEN attributes as well */
14042           {   int i;
14043               initialRulePlies = FENrulePlies;
14044               epStatus[0] = FENepStatus;
14045               for( i=0; i<nrCastlingRights; i++ )
14046                   castlingRights[0][i] = FENcastlingRights[i];
14047           }
14048       EditPositionDone(FALSE);
14049       DisplayBothClocks();
14050       DrawPosition(FALSE, boards[currentMove]);
14051     }
14052   }
14053 }
14054
14055 static char cseq[12] = "\\   ";
14056
14057 Boolean set_cont_sequence(char *new_seq)
14058 {
14059     int len;
14060     Boolean ret;
14061
14062     // handle bad attempts to set the sequence
14063         if (!new_seq)
14064                 return 0; // acceptable error - no debug
14065
14066     len = strlen(new_seq);
14067     ret = (len > 0) && (len < sizeof(cseq));
14068     if (ret)
14069         strcpy(cseq, new_seq);
14070     else if (appData.debugMode)
14071         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14072     return ret;
14073 }
14074
14075 /*
14076     reformat a source message so words don't cross the width boundary.  internal
14077     newlines are not removed.  returns the wrapped size (no null character unless
14078     included in source message).  If dest is NULL, only calculate the size required
14079     for the dest buffer.  lp argument indicats line position upon entry, and it's
14080     passed back upon exit.
14081 */
14082 int wrap(char *dest, char *src, int count, int width, int *lp)
14083 {
14084     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14085
14086     cseq_len = strlen(cseq);
14087     old_line = line = *lp;
14088     ansi = len = clen = 0;
14089
14090     for (i=0; i < count; i++)
14091     {
14092         if (src[i] == '\033')
14093             ansi = 1;
14094
14095         // if we hit the width, back up
14096         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14097         {
14098             // store i & len in case the word is too long
14099             old_i = i, old_len = len;
14100
14101             // find the end of the last word
14102             while (i && src[i] != ' ' && src[i] != '\n')
14103             {
14104                 i--;
14105                 len--;
14106             }
14107
14108             // word too long?  restore i & len before splitting it
14109             if ((old_i-i+clen) >= width)
14110             {
14111                 i = old_i;
14112                 len = old_len;
14113             }
14114
14115             // extra space?
14116             if (i && src[i-1] == ' ')
14117                 len--;
14118
14119             if (src[i] != ' ' && src[i] != '\n')
14120             {
14121                 i--;
14122                 if (len)
14123                     len--;
14124             }
14125
14126             // now append the newline and continuation sequence
14127             if (dest)
14128                 dest[len] = '\n';
14129             len++;
14130             if (dest)
14131                 strncpy(dest+len, cseq, cseq_len);
14132             len += cseq_len;
14133             line = cseq_len;
14134             clen = cseq_len;
14135             continue;
14136         }
14137
14138         if (dest)
14139             dest[len] = src[i];
14140         len++;
14141         if (!ansi)
14142             line++;
14143         if (src[i] == '\n')
14144             line = 0;
14145         if (src[i] == 'm')
14146             ansi = 0;
14147     }
14148     if (dest && appData.debugMode)
14149     {
14150         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14151             count, width, line, len, *lp);
14152         show_bytes(debugFP, src, count);
14153         fprintf(debugFP, "\ndest: ");
14154         show_bytes(debugFP, dest, len);
14155         fprintf(debugFP, "\n");
14156     }
14157     *lp = dest ? line : old_line;
14158
14159     return len;
14160 }